Entity framework 在ASP.NET Web API中序列化对象时出现循环引用错误
我正在用C#编写一个Web API项目,它使用实体框架从数据库中提取数据,将其序列化并发送给客户机 我的项目有两个类,Post和Comment(来自Post的外键) 这些是我的课 邮班:Entity framework 在ASP.NET Web API中序列化对象时出现循环引用错误,entity-framework,asp.net-mvc-4,serialization,asp.net-web-api,circular-reference,Entity Framework,Asp.net Mvc 4,Serialization,Asp.net Web Api,Circular Reference,我正在用C#编写一个Web API项目,它使用实体框架从数据库中提取数据,将其序列化并发送给客户机 我的项目有两个类,Post和Comment(来自Post的外键) 这些是我的课 邮班: public partial class Post { public Post() { this.Attachment = new HashSet<Attachment>(); this.Comment = new HashSet<Comment
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; }
}
我的问题是,当我试图通过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.
[OperationContract]
Comment FindComment(string criteria);
如果我用
[DataContract(IsReference = true)]
错误消失,但序列化只返回注释的ID,而忽略其他字段
对如何解决这个问题有什么建议吗
提前感谢,
Lester您可以通过从Post属性定义中删除virtual来禁用注释类上的延迟加载
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; }
}
这应该可以解决循环引用异常。这里有两种解决方案
解决方案#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
}
}
//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
);
}
}
MetadataType
基本上会将元数据
好友类中的属性添加到注释
中同名的属性中(不是直接添加,但出于我们的目的,它已经足够接近了……这是另一篇文章的主题)。当然,如果您的注释
实体发生了更改,您需要相应地更新它
解决方案#2:
与直接编辑自动生成的文件相比,每次进行更改时都必须编辑第二个文件只是一个小小的改进。幸运的是,还有另一种更容易维护的方法。可以找到详细信息,但作为一个总结,您需要做的就是使用一个附加属性,ReferencePreservingDataContractFormat
来修饰您的OperationContract
。请注意,该页面上提供的代码中有一个微小的错误,它将导致无限递归。正如在文章中所指出的,修复非常简单:只需创建一个新的DataContractSerializer
这种方法的优点是,无论您对注释做了多少更改,您仍然不需要更新任何内容
作为代码示例,假设您正在使用Comment
,如下所示:
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.
[OperationContract]
Comment FindComment(string criteria);
您只需添加
[OperationContract]
[ReferencePreservingDataContractFormat]
Comment FindComment(string criteria);
然后在其他地方需要定义ReferencePreservingDataContractFormat
,如下所示:
[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
}
}
//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
);
}
}
//来自http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx 及https://stackoverflow.com/questions/4266008/endless-loop-in-a-code-sample-on-serialization
公共类ReferencePreservingDataContractFormatAttribute:属性,IOperationBehavior
{
public void AddBindingParameters(操作说明、BindingParameterCollection参数)
{
}
public void ApplyClientBehavior(操作描述,System.ServiceModel.Dispatcher.ClientOperation代理)
{
IOperationBehavior innerBehavior=新引用保留数据契约序列化属性行为(描述);
ApplyClientBehavior(描述,代理);
}
public void ApplyDispatchBehavior(操作描述,System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
IOperationBehavior innerBehavior=新引用保留数据契约序列化属性行为(描述);
ApplyDispatchBehavior(描述、分派);
}
公共无效验证(操作说明)
{
}
}
类referencePreservingDataContractSerializeRopection行为:DataContractSerializeRopection行为
{
公共引用保留DataContractSerializePropertyBehavior(OperationDescription OperationDescription):基(OperationDescription){}
公共重写XmlObjectSerializer CreateSerializer(类型类型、字符串名称、字符串ns、IList knownTypes)
{
返回新的DataContractSerializer(类型、名称、ns、knownTypes、,
0x7FFF,//maxItemsInObjectGraph
false,//ignoreExtensionDataObject
true,//preserveObjectReferences
null//datacontractsubrogate
);
}
公共重写XmlObjectSerializer CreateSerializer(类型类型、XmlDictionaryString名称、XmlDictionaryString ns、IList knownTypes)
{
返回新的DataContractSerializer(类型、名称、ns、knownTypes、,
0x7FFF,//maxItemsInObjectGraph
false,//ignoreExtensionDataObject
true,//preserveObjectReferences
null//datacontractsubrogate
);
}
}
就这样
任何一种方法都可以很好地工作——选择一种适合您的方法。解决方案2应该适用于WCF项目,我在这里测试了它不起作用。@IcyBrk-您一定做错了什么,因为我一直在WCF项目中使用它,而且它工作得很好。这解决了自引用导航属性吗?