7

我正在用 C# 编写一个 Web API 项目,该项目使用实体框架从数据库中提取数据,对其进行序列化并将其发送到客户端。

我的项目有 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.

如果我用注释注释类

[DataContract(IsReference = true)]

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

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

提前致谢,

莱斯特

4

2 回答 2

6

这里有2个解决方案

解决方案#1:

我遇到了同样的问题,所以我用你提到的方式装饰了我的班级DataContract和成员。DataMember但是,我不喜欢直接编辑自动生成的代码,因为每次重新生成文件时都必须重做。为了解决这个问题,我使用了该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:

每次进行更改时都必须编辑第二个文件,这与直接编辑自动生成的文件相比略有改进。幸运的是,还有另一种更容易维护的方法。可以在此处找到详细信息,但作为总结,您需要做的就是使用附加属性来装饰OperationContract正在消费的. 请注意,该页面上提供的代码中存在一个会导致无限递归的小错误。如本文所述,修复非常简单:根本不用递归,只需创建一个新的CommentReferencePreservingDataContractFormatDataContractSerializer

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

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

[OperationContract]
Comment FindComment(string criteria);

您需要做的就是添加

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

然后在其他地方你需要定义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
            );
    }
}

就是这样!

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

于 2014-03-04T23:05:44.483 回答
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; }
}

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

于 2013-07-19T13:28:18.093 回答