Monday, September 29, 2014

Entity circular reference issue in wcf

WCF Services uses DataContractSerializer by default. when WCF tries to serialize the entities that have relationships with other entities, it embeds them inside each other multiple times by default. When we use Entity Framework and poco entities that have bi-directional relationships, if we need to return those entities from WCF methods, we get exception and circular references cause the serialization to fail. we need to tell DataContractSerializer to preserve the object references and act smarter and link objects together rather than copying multiple times.
There is a parameter in DataContractSerializer constructor that can be set to true to preserve the object references.

I found two ways to fix that issue:

1- using [DataContract(IsReference = true)] attribute on the entity

[DataContract(IsReference = true)]
    public class Customer
    { 
        [DataMember] 
        public string Name { get; set; } 

        [DataMember] 
        public Order Order { get; set; } 
    } 


2- using custom attribute on service operations


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;

namespace MyWcfService
{
    [Serializable]
    public class DataContractSerializerCircularReferenceFixAttribute : Attribute, IOperationBehavior
    {
        private readonly DataContractSerializerCircularReferenceFixOperationBehavior _innerOperationBehavior;

        public DataContractSerializerCircularReferenceFixAttribute()
        {
        }

        public DataContractSerializerCircularReferenceFixAttribute(OperationDescription operation)
        {
            _innerOperationBehavior = new DataContractSerializerCircularReferenceFixOperationBehavior(operation);
        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
            if (_innerOperationBehavior == null)
                return;

            (_innerOperationBehavior as IOperationBehavior).AddBindingParameters(operationDescription, bindingParameters);
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            ReplaceDataContractSerializerOperationBehavior(operationDescription);
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            ReplaceDataContractSerializerOperationBehavior(operationDescription);
        }

        public void Validate(OperationDescription operationDescription)
        {
            if (_innerOperationBehavior == null)
                return;

            (_innerOperationBehavior as IOperationBehavior).Validate(operationDescription);
        }

        private void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
        {
            var operationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();

            if (operationBehavior == null)
                return;

            description.Behaviors.Remove(operationBehavior);
            description.Behaviors.Add(new DataContractSerializerCircularReferenceFixOperationBehavior(description));
        }
    }

    class DataContractSerializerCircularReferenceFixOperationBehavior : DataContractSerializerOperationBehavior
    {

        public DataContractSerializerCircularReferenceFixOperationBehavior(OperationDescription operation)
            : base(operation)
        {

        }

        public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
        {

            return new DataContractSerializer(type, name, ns, knownTypes, this.MaxItemsInObjectGraph, this.IgnoreExtensionDataObject, true, this.DataContractSurrogate);

        }

        public override XmlObjectSerializer CreateSerializer(Type type, System.Xml.XmlDictionaryString name, System.Xml.XmlDictionaryString ns, IList<Type> knownTypes)
        {

            return new DataContractSerializer(type, name, ns, knownTypes, this.MaxItemsInObjectGraph, this.IgnoreExtensionDataObject, true, this.DataContractSurrogate);

        }

    } 
}


// WCF Service--------------------------------------


[ServiceContract]
public class MyService
{
    [OperationContract]
    [DataContractSerializerCircularReferenceFix]
    public Customer GetCustomer(string id){
      //code here
    }
}