Ian Cooper

Sponsors

The Lounge

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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.

 


Posted Sun, Mar 9 2008 7:01 PM by Ian Cooper
Filed under:

[Advertisement]

Comments

Ian Cooper [MVP] wrote Architecting LINQ To SQL Applications, part 7
on Sun, Mar 9 2008 3:57 PM

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

Jim Wooley wrote re: Architecting LINQ To SQL Applications, part 6
on Sun, Mar 9 2008 5:43 PM

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.

Ian Cooper wrote re: Architecting LINQ To SQL Applications, part 6
on Sun, Mar 9 2008 6:59 PM

@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).

Christopher Steen wrote Link Listing - March 9, 2008
on Mon, Mar 10 2008 2:12 AM

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

Christopher Steen wrote Link Listing - March 9, 2008
on Mon, Mar 10 2008 2:12 AM

Link Listing - March 9, 2008

Dew Drop - March 10, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - March 10, 2008 | Alvin Ashcraft's Morning Dew
on Mon, Mar 10 2008 8:00 AM

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

TrackBack wrote http://dotnetjunkies.com/weblog/csteen/archive/2008/03/10/452817.aspx
on Tue, Mar 11 2008 6:29 AM
Jeremy Holt wrote re: Architecting LINQ To SQL Applications, part 6
on Fri, Mar 14 2008 7:41 PM

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?

Gerlei Márton blogja az Architektúra Fórumon wrote Architecting LINQ to SQL applications - by Ian Cooper
on Wed, May 7 2008 5:55 PM

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

LaptopHeaven: blogging that bytes wrote Architecting LINQ To SQL Applications
on Tue, Jul 1 2008 9:38 AM

Ian Cooper has written a blog series on Architecting LINQ to SQL applications. I thought I would create an index to each part. Architecting LINQ To SQL Applications, Part 1 - Introduction Architecting LINQ To SQL Applications, Part 2 - Layered Architectures

Russell East wrote re: Architecting LINQ To SQL Applications, part 6
on Tue, Aug 19 2008 7:05 PM

Fantastic series. I use similar approaches to the ones use are promoting, SoC, TDD/BDD and DDD etc. I have been mainly using nhibernate and a little bit of Castle ActiveRecord over the last year or so. I did dismiss Linq for SQL a while a ago when i played with it.  The designer and the generated code is not a good sales point for developers that like to write great code and value their domain model. From reading this series, i am going to late in bed tonight as i have to cut some code and getting something running with Linq for SQL ;-).

I do have a question that i hope you can give me guidence on. Though its probably simple. You are putting all your mappings into one file. As the domain grows so will this file and i assume that over time this will slow things down a bit. Can the mapping files be split up so you have one mapping file per type like nhibernate does?

Again, great series.

Ian Cooper [MVP] wrote Persistence Ignorance with LINQ To SQL
on Tue, Mar 3 2009 9:01 AM

I do not usually just re-point to older blogs, but some people including Don Smith were asking in the

Community Blogs wrote Persistence Ignorance with LINQ To SQL
on Tue, Mar 3 2009 9:14 AM

I do not usually just re-point to older blogs, but some people including Don Smith were asking in the

Add a Comment

(required)  
(optional)
(required)  
Remember Me?