Circular Reference error when serializing objects in ASP.NET Web API
asp.net-mvc-4, asp.net-web-api, circular-reference, entity-framework, serialization
Solution
Here are 2 solutions
Solution #1:
I had this same problem and so I decorated my class with `DataContract` and the members with `DataMember` like you mention. HOWEVER, I don't like editing auto-generated code directly because I have to redo it every time I regenerate the file. In order to get around this, I used the `MetadataType` attribute. In your case, it would look like this...
First, you will keep the auto generated entity as is:
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public virtual Post Post { get; set; }
}
Next, in another file, you will create another partial class and decorate it like this:
[MetadataType(typeof(Metadata))]
[DataContract(IsReference = true)]
public partial class Comment
{
private class Metadata
{
[DataMember]
public int CommentId { get; set; }
[DataMember]
public string Content { get; set; }
[DataMember]
public System.DateTime Posted { get; set; }
[DataMember]
public bool Approved { get; set; }
[DataMember]
public int AnswersTo { get; set; }
[DataMember]
public int PostId { get; set; }
[DataMember]
public virtual Post Post { get; set; } // you can remove "virtual" if you wish
}
}
`MetadataType` will essentially add the attributes from the `Metadata` buddy class to the ones with the same name in `Comment` (not directly, but for our purposes, it's close enough... that's a topic for a different post). Of course, if your `Comment` entity changes, you'll need to update this accordingly.
Solution #2:
Having to edit your second file every time you make a change is only a slight improvement from directly editing auto-generated files. Fortunately, there is another approach that is much easier to maintain. Details can be found here but as a summary, all you need to do is decorate your `OperationContract` that is consuming `Comment` with an additional attribute, `ReferencePreservingDataContractFormat`. Note that there is a slight error in the code provided on that page that would cause infinite recursion. As noted in this post, the fix is quite simple: instead of recursing at all, just create a new `DataContractSerializer`
The advantage to this approach is that no matter how much you change `Comment`, you still don't need update anything.
As an example for your code, let's say you are using `Comment` as follows:
[OperationContract]
Comment FindComment(string criteria);
All you need to do is add
[OperationContract]
[ReferencePreservingDataContractFormat]
Comment FindComment(string criteria);
And then somewhere else you need to define `ReferencePreservingDataContractFormat` which will look like this:
//From http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx and https://stackoverflow.com/questions/4266008/endless-loop-in-a-code-sample-on-serialization
public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyClientBehavior(description, proxy);
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyDispatchBehavior(description, dispatch);
}
public void Validate(OperationDescription description)
{
}
}
class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription) { }
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFF, //maxItemsInObjectGraph
false, //ignoreExtensionDataObject
true, //preserveObjectReferences
null //dataContractSurrogate
);
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFF, //maxItemsInObjectGraph
false, //ignoreExtensionDataObject
true, //preserveObjectReferences
null //dataContractSurrogate
);
}
}
And that's it!
Either method will work just fine--pick the one that works for you.
Problem
I'm writing a Web API project in C# that uses Entity Framework to pull data from a DB, serialize it and send it to a client. My project has 2 classes, Post and Comment (foreign key from Post). These are my classes. Post class: ``` public partial class Post { public Post() { this.Attachment = new HashSet<Attachment>(); this.Comment = new HashSet<Comment>(); } public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public System.DateTime Created { get; set; } public Nullable<System.DateTime> Modified { get; set; } public virtual ICollection<Attachment> Attachment { get; set; } public virtual ICollection<Comment> Comment { get; set; } } ``` Comment class: ``` public partial class Comment { public int CommentId { get; set; } public string Content { get; set; } public System.DateTime Posted { get; set; } public bool Approved { get; set; } public int AnswersTo { get; set; } public int PostId { get; set; } public virtual Post Post { get; set; } } ``` My problem is that when I try to get via Web API a Post, it spits me the following error: ``` Object graph for type 'APIServer.Models.Comment' contains cycles and cannot be serialized if reference tracking is disabled. ``` And when I try to get a Comment via Web API, the error is as follows: ``` Object graph for type 'System.Collections.Generic.HashSet`1[[APIServer.Models.Comment, APIServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled. ``` If I annotate the Comment class with ``` [DataContract(IsReference = true)] ``` the errors disappear, but the serialization only returns the ID of the comment and ignores the other fields. Any suggestions on how to solve this? Thanks in advance, Léster