Circular Reference Serialization in Entity Framework

Circular Reference issues when serializing more complicated data objects is commonplace. A typical way of dealing with this is to add DataContract serialization annotation similar to:

[DataContract(IsReference=true)]
    public partial class Test
    {
        [DataMember]
        public long ID { get; set; }
        [DataMember]
        public string Description { get; set; }
        [DataMember]
        public string TestName { get; set; }
     }

The DataContractSerializer is capable of serializing object trees that contain circular references. This fix allows the user to get around this pesky issue; however, in the case of the entity framework (database first approach), we will find modifying a class directly like this a problem, due in part to its nature as auto-generated code. If you were to modify the database in some way, and try to bring the model in line with it, you will find that all annotation is lost.

The solution to this issue lies in the Entity Framework template. This will usually be titledModel1.ttor whatever you may have named the model with the.ttextension. It is this file which is used to generate each class. We should find a section that looks like this:

<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#> <#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>
{
<#
    var propertiesWithDefaultValues = entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity && p.DefaultValue != null);
    var collectionNavigationProperties = entity.NavigationProperties.Where(np => np.DeclaringType == entity && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
    var complexProperties = entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == entity);
...

It is this that creates each class, all that's needed is to add the DataContract(IsReference=true)] annotation before this section:

[DataContract(IsReference=true)]
<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>
{
<#
    var propertiesWithDefaultValues = entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity && p.DefaultValue != null);
    var collectionNavigationProperties = entity.NavigationProperties.Where(np => np.DeclaringType == entity && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
    var complexProperties = entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == entity);
...

Similarly we need to do the same for the [DataMember] annotation. There is a small section that appears like this:

void WriteProperty(string accessibility, string type, string name, string getterAccessibility, string setterAccessibility)
{
#>
    <#=accessibility#> <#=type#> <#=name#> { <#=getterAccessibility#>get; <#=setterAccessibility#>set; }
<#+
}

This is responsible for creating all of the properties, all that's needed is to insert the annotation inside of this method as below:

void WriteProperty(string accessibility, string type, string name, string getterAccessibility, string setterAccessibility)
{
#>
[DataMember]
    <#=accessibility#> <#=type#> <#=name#> { <#=getterAccessibility#>get; <#=setterAccessibility#>set; }
<#+
}

Now the Entity Framework will apply the annotation to all classes that it generates, removing any circular reference errors you may have encountered.

Some code in the template may appear different in the various EF versions, but they should bear a resemblance to these sections.


Paul 29 Oct 2014