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

时间:2013-07-18 08:16:57

标签: entity-framework asp.net-mvc-4 serialization asp.net-web-api circular-reference

我正在用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; }
}

评论类:

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; }
}

我的问题是,当我尝试通过Web API发布邮件时,它会向我发出以下错误:

Object graph for type 'APIServer.Models.Comment' contains cycles and cannot be serialized if reference tracking is disabled.

当我尝试通过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.

如果我用

注释Comment类
[DataContract(IsReference = true)]

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

有关如何解决此问题的任何建议?

提前致谢,

莱斯特

2 个答案:

答案 0 :(得分: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; }
}

接下来,在另一个文件中,您将创建另一个部分类并按如下所示进行装饰:

[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基本上会将Metadata伙伴类中的属性添加到Comment中具有相同名称的属性(不是直接,但就我们的目的而言,它足够接近......这是一个不同帖子的主题)。当然,如果您的Comment实体发生变化,则需要相应更新。

解决方案#2:

每次进行更改时都必须编辑第二个文件,这与直接编辑自动生成的文件相比只是略有改进。幸运的是,还有另一种方法更容易维护。可以找到详细信息here但是作为摘要,您需要做的就是使用其他属性OperationContract来装饰正在消耗Comment的{​​{1}}。请注意,该页面上提供的代码中存在轻微错误,这将导致无限递归。正如this帖子中所述,修复非常简单:只需创建一个新的ReferencePreservingDataContractFormat

而不是递归

这种方法的优势在于,无论您更改DataContractSerializer多少,您仍然不需要更新任何内容。

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

Comment

您需要做的就是添加

[OperationContract]
Comment FindComment(string criteria);

然后在其他地方你需要定义[OperationContract] [ReferencePreservingDataContractFormat] Comment FindComment(string criteria); ,如下所示:

ReferencePreservingDataContractFormat

就是这样!

任何一种方法都可以正常工作 - 选择适合你的方法。

答案 1 :(得分:1)

您可以通过从Post属性定义中删除虚拟来禁用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; }
}

这应该排除循环引用异常。

相关问题