CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Ian Cooper [MVP]


Architecting LINQ To SQL Applications, part 6

Previous: Architecting LINQ To SQL Applications, part 5

Mapping with XML files instead of Attributes

Greg Young pointed out in the comments to the last post that using attributes can clutter your domain objects. Although it is simpler to show attributes first, so that you can relate rolling your own mappings to the designer generated code, I did not want to leave the story incomplete without showing you how to move those mappings into a file in order to keep your domain objects clean.

The correspondence between the mapping file and the attributes is straightforward. Instead of attributes we just have XML elements and instead of properties on those attributes, we have attributes on our XML elements.

First of all we need to create a text file to hold our mappings. We call it Keysafe.map. Next we need to indicate the xml encoding:

<?xml version="1.0" encoding="utf-8"?>

Now we need to open up a Database element, which will form the root of our mapping.

<Database Name="northwind" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
</Database>

Within the Database element we need to add a Table element for each entity we wish to map (equivalent to our [Table] attribute).

<?xml version="1.0" encoding="utf-8"?>
<Database Name="northwind" xmlns="
http://schemas.microsoft.com/linqtosql/mapping/2007">
 <Table Name="dbo.Category">
 </Table>
</Database>

Because we are not using an attribute we have to tell LINQ To SQL what type our table maps to explicitly:

<?xml version="1.0" encoding="utf-8"?>
<Database Name="northwind" xmlns="
http://schemas.microsoft.com/linqtosql/mapping/2007">
 <Table Name="dbo.Category">
  <Type Name="KeySafeDomain.Category">
  </Type>
 </Table>
</Database>

Then we need to map out our Columns. Again because we are not associating our attribute with a member, we have to explicitly indicate the member.

<?xml version="1.0" encoding="utf-8"?>
<Database Name="northwind" xmlns="
http://schemas.microsoft.com/linqtosql/mapping/2007">
 <Table Name="dbo.Category">
  <Type Name="KeySafeDomain.Category">
   <Column Name="Id" Member="Id" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" UpdateCheck="Never" AutoSync="OnInsert" />
   <Column Name="Name" Member="Name" DbType="NVarChar(50) NOT NULL" CanBeNull="false" UpdateCheck="Never" />
   <Column Name="ParentId" Member="ParentId" DbType="Int" UpdateCheck="Never" />
   <Column Name="Version" Member="Version" DbType="rowversion NOT NULL" CanBeNull="false" IsDbGenerated="true" IsVersion="true" AutoSync="Always" />
  </Type>
 </Table>
</Database>

As before the DbType information is there to help us generate the Db from our domain model.

We also need to map out the associations between our classes. Again the conversion between the attribute based model and our XML model is straightforward.

<Association Member="Children" Storage="children" ThisKey="ParentId" OtherKey="Id"/>

<Association Member="Parent" Storage="parent" ThisKey="ParentId"/>

<Association Member="Systems" Storage="systems" OtherKey="CategoryId"/>

At this point it is just grunt work to translate our previous attribute based mappings into an XML mapping file.

In the end our mapping looks like this:

<?xml version="1.0" encoding="utf-8"?>

<Database Name="northwind" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">

<Table Name="dbo.Category">

<Type Name="KeySafeDomain.Category">

<Column Name="Id" Member="Id" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" UpdateCheck="Never" AutoSync="OnInsert" />

<Column Name="Name" Member="Name" DbType="NVarChar(50) NOT NULL" CanBeNull="false" UpdateCheck="Never" />

<Column Name="ParentId" Member="ParentId" DbType="Int" UpdateCheck="Never" />

<Column Name="Version" Member="Version" DbType="rowversion NOT NULL" CanBeNull="false" IsDbGenerated="true" IsVersion="true" AutoSync="Always" />

<Association Member="Children" Storage="children" ThisKey="ParentId" OtherKey="Id"/>

<Association Member="Parent" Storage="parent" ThisKey="ParentId"/>

<Association Member="Systems" Storage="systems" OtherKey="CategoryId"/>

</Type>

</Table>

<Table Name="dbo.ITSystem">

<Type Name="KeySafeDomain.ITSystem">

<Column Name="CategoryId" Member="CategoryId" DbType="Int NOT NULL" UpdateCheck="Never" />

<Column Name="Comments" Member="Comments" DbType="NVarChar(4000)" UpdateCheck="Never" />

<Column Name="Name" Member="Name" DbType="NVarChar(50) NOT NULL" CanBeNull="false" UpdateCheck="Never" />

<Column Name="Id" Member="Id" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" UpdateCheck="Never" AutoSync="OnInsert" />

<Column Name="Version" Member="Version" DbType="rowversion NOT NULL" CanBeNull="false" IsDbGenerated="true" IsVersion="true" AutoSync="Always" />

<Association Member="Keys" Storage="keys" OtherKey="SystemId"/>

<Association Member="Category" Storage="category" OtherKey="Id" ThisKey="CategoryId"/>

</Type>

</Table>

<Table Name="dbo.Key">

<Type Name="KeySafeDomain.Key">

<Column Name="Id" Member="Id" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" UpdateCheck="Never" AutoSync="OnInsert" />

<Column Name="Password" Member="Password" DbType="NVarChar(50) NOT NULL" CanBeNull="false" UpdateCheck="Never" />

<Column Name="SystemId" Member="SystemId" DbType="Int NOT NULL" UpdateCheck="Never" />

<Column Name="UserName" Member="UserName" DbType="NVarChar(50) NOT NULL" CanBeNull="false" UpdateCheck="Never" />

<Column Name="Version" Member="Version" DbType="rowversion NOT NULL" CanBeNull="false" IsDbGenerated="true" IsVersion="true" AutoSync="Always" />

<Association Member="System" Storage="system" OtherKey="Id" ThisKey="SystemId"/>

</Type>

</Table>

</Database>

We can then delete all of our mappings from our domain model so that our model is clean.

    public class Category
    {
        private EntitySet<Category> children = new EntitySet<Category>();
        public int Id { get; set; }
        public string Name {get;set; }
        public int? ParentId { get; set; }
        private EntityRef<Category> parent;
        private EntitySet<ITSystem> systems = new EntitySet<ITSystem>();
        public byte[] Version {get;set;}
        ...
    }
   
    public class Key
    {
        public int Id { get; set; }
        public string Password { get; set; }
        public int SystemId { get; set; }
        private EntityRef<ITSystem> system = default(EntityRef<ITSystem>);
        public string UserName { get; set; }
        public byte[] Version {get;set;}
        ...
    }
   
    public class ITSystem
    {
        public int CategoryId { get; set; }
  private EntityRef<Category> category = default(EntityRef<Category>);
        public string Comments {get;set;}
        public string Name {get;set; }
        public int Id { get; set; }
        private EntitySet<Key> keys = new EntitySet<Key>();
        public byte[] Version {get;set;}
        ...
    }

Managing the Mapping File 

I often embed the file into the dll that contains the model. The upside here is that you don't need to worry about deployment, but the downside is that you cannot change the mapping without re-issuing the DLL. If you embed the mapping file, you will need some code to read it, so that you can pass it into the DataContext. I use a helper class like this:

    public static class Mapping
    {
        public static XmlMappingSource GetMapping()
        {
            XmlMappingSource mapping;
            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("KeySafeDomain.Mappings.Keysafe.map"))
            {
                mapping = XmlMappingSource.FromStream(stream);
            }

            return mapping;
        }
    }

Then we just grab the mapping when we construct our typesafe DataContext:

        public KeySafeContext() : base(ConfigurationManager.ConnectionStrings[DbName].ConnectionString, KeySafeDomain.Mappings.Mapping.GetMapping()) 
        {
            Systems = GetTable<ITSystem>();
            Keys = GetTable<Key>();
            Categories = GetTable<Category>();

            DataLoadOptions dataLoadOptions = new DataLoadOptions();
            dataLoadOptions.LoadWith<ITSystem>(s=>s.Keys);
        }


With that done we can re-run our tests to check that everything passes.

 


Published Mar 09 2008, 07:01 PM by Ian Cooper
Filed under:

Comments

Ian Cooper [MVP] said:

Previously: Architecting LINQ To SQL Applications, part 6 The topic of managing entity lifetimes is an

# March 9, 2008 3:57 PM

Jim Wooley said:

If you don't want to hand craft the XML, you can use SQL Metal to generate the XML structures from your database schema. Once you have that in place, you can the maintain the XML file. This has the advantage not only of keeping the classes de-cluttered as Greg pointed out, but also keeps all of the mapping consolidated to a centralized location which may make maintenance easiler. An additional benefit of the XML mapping is that you can change the mappings dynamically at run-time without requiring a re-compilation. We discuss the pros and cons of these various mapping options in our LINQ In Action book.

BTW, nice series. Keep them coming.

# March 9, 2008 5:43 PM

Ian Cooper said:

@Jim

For sure, but I would suggest that people opt for working from the domain and generating the Db over generating the mappings from the Db schema. This approach supports domain and test-driven development better than schema first approaches (you can't drive your schema with tests based on user stories in quite the same way).

# March 9, 2008 6:59 PM

Christopher Steen said:

ASP.NET LinkButtons in UpdatePanel cause full postback unless you give them IDs [Via: alexcampbell ]...

# March 10, 2008 2:12 AM

Christopher Steen said:

Link Listing - March 9, 2008

# March 10, 2008 2:12 AM

Dew Drop - March 10, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - March 10, 2008 | Alvin Ashcraft's Morning Dew

# March 10, 2008 8:00 AM

TrackBack said:

# March 11, 2008 6:29 AM

Jeremy Holt said:

How would you map a complex property to the database? Say your entity has a property of type PrimaryKey:

struct PrimaryKey{

   int Value {get; set:}

   bool IsValidKey {get;}

}

and you want to map PrimaryKey.Value to the database's  <Column Name="Id" Member="Id" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" UpdateCheck="Never" AutoSync="OnInsert" />

Is there any way to do this?

# March 14, 2008 7:41 PM

Gerlei Márton blogja az Architektúra Fórumon said:

Egy LINQ-s problémával küszködve találtam az alábbi cikksorozatot, és szerintem eléggé hasznos. Sok mindenről

# May 7, 2008 5:55 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!