在ASP.NET Web API中序列化对象时出现循环引用错误

Lés*_*ter 7 serialization entity-framework circular-reference asp.net-mvc-4 asp.net-web-api

我正在用C#编写一个Web API项目,它使用Entity Framework从数据库中提取数据,将其序列化并发送给客户端.

我的项目有2个类,Post和Comment(来自Post的外键).

这些是我的课程.

发布课程:

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; }
}
Run Code Online (Sandbox Code Playgroud)

评论类:

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; }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,当我尝试通过Web API获取帖子时,它会向我吐出以下错误:

Object graph for type 'APIServer.Models.Comment' contains cycles and cannot be serialized if reference tracking is disabled.
Run Code Online (Sandbox Code Playgroud)

当我尝试通过Web API获取注释时,错误如下:

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.
Run Code Online (Sandbox Code Playgroud)

如果我用Comment注释Comment类

[DataContract(IsReference = true)]
Run Code Online (Sandbox Code Playgroud)

错误消失,但序列化只返回注释的ID并忽略其他字段.

关于如何解决这个问题的任何建议?

提前致谢,

莱斯特

Dav*_*vid 6

这是2个解决方案

解决方案#1:

我遇到了同样的问题,所以我装饰了我的班级DataContractDataMember你提到的成员.但是,我不喜欢直接编辑自动生成的代码,因为每次重新生成文件时我都要重做它.为了解决这个问题,我使用了该MetadataType属性.在你的情况下,它看起来像这样......

首先,您将按原样保留自动生成的实体:

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; }
}
Run Code Online (Sandbox Code Playgroud)

接下来,在另一个文件中,您将创建另一个分部类并将其装饰如下:

[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
    }
}
Run Code Online (Sandbox Code Playgroud)

MetadataType本质上会将Metadata伙伴类中的属性添加到具有相同名称的属性中Comment(不是直接的,但对于我们的目的,它足够接近......这是不同帖子的主题).当然,如果您的Comment实体发生变化,您需要相应地更新它.

解决方案#2:

每次进行更改时都必须编辑第二个文件,这与直接编辑自动生成的文件相比只是略有改进.幸运的是,还有另一种方法更容易维护.详细信息可以发现在这里,但作为一个总结,所有你需要做的是装饰你OperationContract是消耗Comment带有一个附加属性ReferencePreservingDataContractFormat.请注意,该页面上提供的代码中存在轻微错误,这将导致无限递归.正如指出的这个帖子,解决方法是相当简单:不是在所有的递归,只需创建一个新的DataContractSerializer

这种方法的优点是无论你改变了多少Comment,你仍然不需要更新任何东西.

作为代码的示例,假设您使用Comment如下:

[OperationContract]
Comment FindComment(string criteria);
Run Code Online (Sandbox Code Playgroud)

您需要做的就是添加

[OperationContract]
[ReferencePreservingDataContractFormat]
Comment FindComment(string criteria);
Run Code Online (Sandbox Code Playgroud)

然后你需要定义的其他地方ReferencePreservingDataContractFormat看起来像这样:

//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
            );
    }
}
Run Code Online (Sandbox Code Playgroud)

就是这样!

这两种方法都可以正常工作 - 选择适合你的方法.


Jac*_*man 1

您可以通过从 Post 属性定义中删除 virtual 来禁用 Comment 类上的延迟加载...

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; }
}
Run Code Online (Sandbox Code Playgroud)

这应该可以解决循环引用异常。