Thursday, September 25, 2008

Bidirectional Serialization of LINQ to SQL Object Graphs with Damien Guard’s T4 Template in VS 2008 SP1

Summary

The Windows Communication Foundation (WCF) team made a minor modification to the System.Runtime.Serialization.DataContractSerializer class at the behest of the Entity Framework (EF) team to enable “bidirectional” (full object graph) serialization and deserialization of EF object graphs. You specify bidirectional serializtion by adding an IsReference = true argument to the [DataContract()] attribute of EntityRef associations in .NET 3.5 SP1.

A very simple change to LINQ to SQL’s class-generation template could have given LINQ to SQL similar capabilities to serialize complete object graphs that contain cyclic references. The Data Programmability group (DPG) modified LINQ to SQL to support some new SQL Server 2008 datatypes but chose not to enable serialization of complete object graphs.

LINQ to SQL developer Damien Guard, who joined the DPG in May 2008, announced the availability of a customizable T4 template to generate LINQ to SQL classes from the *.dbml file in his LINQ to SQL T4 template reloaded post of July 23, 2008.

A couple of minor modifications to the L2ST4.tt template file and addition of a reference to and using directive for the System.Runtime.Serialization namespace enables full-object-graph serialization of LINQ to SQL entities. This brings parity to both VS 2008 OR/M tools in the serialization department.

Updated 9/25/2008: Damien Guard released the latest (v.74) update to his LINQ to SQL template for Visual Studio 2008 on 9/25/2008. This Text Template Transformation Toolkit (T4) version offers the following new features (quoting Damien):

  • Inheritance - generates subclasses with all properties and code mappings.
  • VB.NET - CSharpDataContext.tt is joined by a VB.NET emitting VBNetDataContext.tt.
  • DataContract SP1 – additional mode to emit SP1-compatible DataContract serialization via Roger Jennings.
  • Composite keys – both as the primary key and as a foreign key in an association.
  • Type attributes – the data context and entity types can now be sealed or abstract as well as public, private, protected, internal or protected internal.
  • Associations – prevents foreign key values changing once the object association is made and updates parent side of one-to-many associations.
  • Stored procedures – generates the method wrappers complete with parameters etc.

You can download the updated (v.74) DamienGuardT4.sln project here (290 KB zip file from Microsoft Skydrive).

Background

In my Serializing Object Graphs Without and With References post of 11/21/2007, I described the differences between .NET 3.5’s DataContractSerializer (DCS), which was then unable to serialize complete object graphs, and the NetDataContractSerializer (NDCS), which could handle cyclic references but included CLR type information in the serialized XML. Substituting CLR types for XML Schema’s datatypes made the serialized XML “non-standard.”

The post included a link to a Customers-Orders-Order_Details tree serialized with DCS (NwindCustsDCS.xml, 1.1 MB) and a similar tree serialized with NDCS (NwindGraphNDCS.xml, 410 KB), which also included associated Employee, Shipper, Product, Category and Supplier EntitySets. The

The z:Type="System.Collections.Generic.List`1[[NwindObjects.MainForm+Customer, NwindObjects, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"

attribute requires that the client have a matching local type for deserialization to succeed.

Note: The 11/21/2007 post was a followup to my LINQ to SQL and Entity Framework XML Serialization Issues with WCF - Part 1 of 10/9/2007, which dealt primarily with a bug in LINQ to SQL serialization in Orcas Beta 2.

Setting LINQ to SQL’s Serialization Mode property of the *.dbml file to Unidirectional causes the class code generation template to apply [DataContract()] attributes to entity classes and [DataMember()] attributes to entity properties, which enables them to participate in WCF 3.5 XML Web service scenarios. Unidirectional serialization also applies [DataMember()] attributes to EntitySets of associated entities in 1:many relationships but not to the EntityRefs of many:1 relationships.

My Is the ADO.NET Team Abandoning LINQ to SQL? post of May 23, 2008 compared the new features added by SP1 to EF and LINQ to SQL. LINQ to SQL got the short end of the stick.

Taking Advantage of Damien’s T4 Template

Updated 9/25/2008: To implement bidirectional serialization with LINQ to SQL and Damien’s Text Template Transformation Toolkit (T4) template v.74+ do the following:

  1. Add the L2ST4.ttinclude and either CSharpDataContext.tt or VBNetDataContext.tt to your project depending on your language type.
  2. Rename xDataContext.tt to match your DBML file but with .tt extension instead of .dbml and include it (and, optionally, the DbmlName.tt file) in your project.
  3. Set the existing DbmlName.designer.cs/vb Build Action property to None to ignore LINQ to SQL’s built-in code generation with the MSLinqToSQLGenerator custom tool.
  4. Right-click the DbmlName.tt file icon in Solution Explorer and choose Run Custom Tool to generate the DbmlName.generated.cs/.vb class file.
  5. If you’re using design-time databound controls, add the project’s namespace to the DbmlName.generated.cs/.vb file.
  6. Press F5 to build and run the project with code generated by the T4 template.

Add the following code behind Form1:

private void Form1_Load(object sender, EventArgs e)
{
    using (NorthwindDataContext ctxNwind = new NorthwindDataContext())
    {
        DataLoadOptions lo = new DataLoadOptions();
        lo.LoadWith<Order>(o => o.Order_Details);
        lo.LoadWith<Order>(c => c.Customer);
        ctxNwind.LoadOptions = lo;
        List<Order> OrderList = ctxNwind.Orders.ToList();

        var dcs = new DataContractSerializer(typeof(List<Order>));

        FileStream fsc = new FileStream("NwindOrdersGraphDCS.xml", 
            FileMode.Create);
        dcs.WriteObject(fsc, OrderList);
        fsc.Close();

        FileStream fso = new FileStream("NwindOrdersGraphDCS.xml", 
            FileMode.Open, FileAccess.Read);
        OrderList = (List<Order>)(dcs.ReadObject(fso));
        fso.Close();
    }
}

Press F5 to build and run the project and generate the NwindOrderGraphDCS.xml file (1.0 MB) in the …\bin\Debug folder. Here’s the project’s Spartan UI:

You can download the updated DamienGuardT4.sln project here (290 KB). The project installs into a \DamienGuardT4 folder and expects the Northwind sample database to be attached to a local instance of SQL Server 2005 or 2008 Express.

1 comments:

Sean said...

This works great. If you are planning to send them over WCF, make sure you add:

myDataContext.DeferredLoadingEnabled = false;

...to your data context. If you don't, the serialization will die and it won't tell you anything useful.

Also, you can add this to your code to detect runtime serialization exceptions that would otherwise get hidden in a WCF call:

DataContractSerializer ser = new DataContractSerializer(typeof(MyObject));
ser.WriteObject(Stream.Null, myObject);