Thursday, 23 June 2011

Serializing LINQ-to-SQL classes example with deep cloning

Serializing Linq-To-Sql classes isn't as straight forward as you might imagine. You may have got as far as attempting to serialize a class and found an error stating Linq.EntityRef cannot be serialized. This post explains to to serialize Linq-To-Sql entities using a cloning example.


For this example, I will be taking a deep clone of a Linq-To-Sql object and all of its associated properties. To do this, I will use serialization and utilise the ICloneable interface.

This example assumes you already have a Linq-To-Sql classes file (.dbml) with some tables/class information within the designer...

1. Open the Linq-To-Sql designer (.dbml) and view the main properties. Change the 'Serialzation Mode' to Unidirectional. This decorates our Linq-To-Sql entities with serialization capabilities.

2. Create a partial class for the Linq-To-Sql class you wish to clone. This allows you to extend the functionality of a Linq-To-Sql class as all classes are partial.

3. Add the [Serializable] attribute to the header of the class. This marks the class as serializable.

4. Implement the ICloneable inteferface. We can now override the Clone() method. When this method is called, we need to apply the serialization process to the object we wish to clone.

5. When serializing a Linq-To-Sql class, we cannot use the BinaryFormatter. Linq-To-Sql classes simply do not support this. Instead, we can utilise the NetDataContractSerializer. This will suit out requirements for the serialization of Linq-To-Sql entities.

6. and your done!



Serialzation code snippet
/// <summary>
/// Linq-To-Sql partial class, extends the 'Event' entity/class.
/// Note: Also changed Serialzation mode in designer to Unidirectional
/// </summary>
[Serializable]
public partial class Event : ICloneable
{
    /// <summary>
    /// Overrides the Clone method within the IClonable interface
    /// to performing object cloning
    /// </summary>
    /// <returns>A clone of the incoming object</returns>
    public object Clone()
    {
        return CloningFunctions.CloneObject(this);
    }
}
 
 
/// <summary>
/// Provides functions aiding object cloning
/// </summary>
public class CloningFunctions
{
    /// <summary>
    /// Clones an  object using the NetDataContractSerializer
    /// </summary>
    /// <param name="obj">Incoming object to clone</param>
    /// <returns>A clone of the incoming object</returns>
    public static object CloneObject(object obj)
    {
        using (MemoryStream memStream = new MemoryStream())
        {
            NetDataContractSerializer formatter = new NetDataContractSerializer(new StreamingContext(StreamingContextStates.Clone));
            formatter.Serialize(memStream, obj);
            memStream.Seek(0, SeekOrigin.Begin);
            return formatter.Deserialize(memStream);
        }
    }
}

1 comment:

Don Ruiter said...

Thanks for this. It works great.
But to use it in an insert/update db record scenario it doesn't invoke the property changing events, so the db record doesn't get changed.

How to get those property changing events fired up? Should I walk through all the members of the class (reflection?)and set them?

Below code exmaple of a dbml generated property of a table field. As one can see the set-part of the property contains the changing events.
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Id", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
[global::System.Runtime.Serialization.DataMemberAttribute(Order=1)]
public System.Guid Id
{
get
{
return this._Id;
}
set
{
if ((this._Id != value))
{
this.OnIdChanging(value);
this.SendPropertyChanging();
this._Id = value;
this.SendPropertyChanged("Id");
this.OnIdChanged();
}
}
}