Circular reference error while serializing objects in ASP.NET Web API

I am writing a C # web API project that uses the Entity Framework to extract data from a database, serialize and send it to a client.

In my project there are 2 classes, a message and a comment (foreign key from the message).

These are my classes.

Publication 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 a message through the Web API, it causes 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 through the web API, the error looks like this:

 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 comment on the Comment class with

 [DataContract(IsReference = true)] 

errors disappear, but serialization returns a comment identifier and ignores other fields.

Any suggestions on how to solve this?

Thanks in advance,

Leicester

+7
serialization asp.net-web-api entity-framework asp.net-mvc-4 circular-reference
source share
2 answers

Here are 2 solutions

Solution No. 1:

I had the same issue, and so I graced my DataContract class with the DataMember members as you mentioned. HOWEVER, I do not like to edit automatically generated code directly, because I have to repeat it every time I regenerate the file. To get around this, I used the MetadataType attribute. In your case, it will look like this ...

First, you save the automatically generated object 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 as follows:

 [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 those of the same name in Comment (not directly, but for our purposes it is close enough ... which is the topic for another after). Of course, if your Comment object changes, you will need to update it accordingly.

Solution No. 2:

When you change your second file every time you make changes, this is only a slight improvement from directly editing automatically 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 , which consumes Comment with the optional ReferencePreservingDataContractFormat attribute. Please note that there is a small error in the code presented on this page that will lead to infinite recursion. As noted in this post, the fix is ​​pretty simple: instead of recursing at all, just create a new DataContractSerializer

The advantage of this approach is that no matter how much you change Comment , you still don't need to update anything.

As an example for your code, suppose you use Comment as follows:

 [OperationContract] Comment FindComment(string criteria); 

All you have to do is add

 [OperationContract] [ReferencePreservingDataContractFormat] Comment FindComment(string criteria); 

And then in another place you need to define a ReferencePreservingDataContractFormat , which will look like this:

 //From http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx and /questions/799848/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 ); } } 

What is it!

Any method will work just fine - choose the one that works for you.

+6
source share

You can disable Lazy Loading in your Comment class by removing the virtual machine from the Post property definition ...

 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 Post Post { get; set; } } 

This should eliminate the circular link exception.

+1
source share

All Articles