JSON.NET的Self referencing loop detected with type的原因以及解决办法

时间:2021-09-10 20:38:22

模型中有循环引用是很常见的。例如,以下模型显示双向导航属性:

   1: public class Category 
   2: { 
   3:     public Category() 
   4:     { 
   5:         Products = new Collection<Product>(); 
   6:     } 
   7:     
   8:     public int Id { get; set; } 
   9:     public string Name { get; set; } 
  10:     public virtual ICollection<Product> Products { get; set; } 
  11: } 
  12:  
  13: public class Product 
  14: { 
  15:     public int Id { get; set; } 
  16:     public string Name { get; set; } 
  17:     public virtual Category Category { get; set; } 
  18: } 

通过生成EF API控制器与Web API一起使用时,默认情况下不起作用。使用json.net序列化器序列化时会发生以下错误:

Self referencing loop detected for property 'Category' with type 
'System.Data.Entity.DynamicProxies.Category_A97AC61AD05BA6A886755C779FD3F96E86FE903ED7C9BA9400E79162C11BA719'. 
Path '[0].Products[0]' 

发生此错误是因为序列化程序不知道如何处理循环引用。(在xml序列化程序中也出现类似的错误)

禁用代理并包含引用

EF代理不适用于POCO数据序列化。有几种  解决方法为了简单起见,我们只是在数据上下文类中禁用它:

   1: public CircularReferenceSampleContext() : base("name=CircularReferenceSampleContext") 
   2: { 
   3:     Database.SetInitializer(new CircularReferenceDataInitializer()); 
   4:     this.Configuration.LazyLoadingEnabled = false; 
   5:     this.Configuration.ProxyCreationEnabled = false; 
   6: } 

但是,在禁用代理之后,导航属性不会被延迟加载。因此,从数据库中检索数据时必须包含参考。将脚手架控制器代码更改为: 

   1public IEnumerable <Product> GetProducts()
   2: { 
   3return db.Products.Include(p => p.Category).AsEnumerable();
   4: } 

包含调用将包含所有记录的参考数据。

修复1:全局忽略循环引用

json.net序列化器支持忽略全局设置的循环引用。一个快速解决方案是将下面的代码放在WebApiConfig.cs文件中:

   1: config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

简单的修复将使序列化程序忽略会导致循环的引用。但是,它有局限性:

  • 数据丢失循环参考信息
  • 该修补程序仅适用于JSON.net
  • 如果存在深度参考链,则无法控制参考级别

修复2:保留全局循环引用

第二个修复与第一个类似。只需将代码更改为:

   1: config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
   2:      = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
   3: config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling
   4:      = Newtonsoft.Json.PreserveReferencesHandling.Objects;

数据形状将在应用此设置后更改。

   1: [{ “$ ID” :“1” , “类别”:{ “$ ID” :“2” , “产品”:[{ “$ ID” :“3” , “类别”:{ “$ REF “:”2“ },”ID“:2, ” 名称“:”酸奶“ },{ ”$ REF“ :”1“ }],”ID“:1, ” 名称“:”日记“ }, ” Id“:1,“名称”:“全脂牛奶” },{ “$ ref”:“3” }]

$ id和$ ref保留所有引用,并使对象图级别保持不变,但客户端代码需要知道形状更改以消费数据,并且它仅适用于JSON.NET序列化程序。

修复3:忽略并保留参考属性

此修补程序在模型类上装饰属性以控制模型或属性级别上的序列化行为。忽略该属性:

   1: public class Category 
   2: { 
   3:     public int Id { get; set; } 
   4:     public string Name { get; set; } 
   5:     
   6:     [JsonIgnore] 
   7:     [IgnoreDataMember] 
   8:     public virtual ICollection<Product> Products { get; set; } 
   9: } 
  JsonIgnore用于JSON.NET,IgnoreDataMember用于XmlDCSerializer。 
为了保持参考:
   1: // Fix 3 
   2: [JsonObject(IsReference = true)] 
   3: public class Category 
   4: { 
   5:     public int Id { get; set; } 
   6:     public string Name { get; set; } 
   7:  
   8:     // Fix 3 
   9:     //[JsonIgnore] 
  10:     //[IgnoreDataMember] 
  11:     public virtual ICollection<Product> Products { get; set; } 
  12: } 
  13:  
  14: [DataContract(IsReference = true)] 
  15: public class Product 
  16: { 
  17:     [Key] 
  18:     public int Id { get; set; } 
  19:  
  20:     [DataMember] 
  21:     public string Name { get; set; } 
  22:  
  23:     [DataMember] 
  24:     public virtual Category Category { get; set; } 
  25: } 

[JsonObject(IsReference = true)]适用于JSON.NET,[DataContract(IsReference = true)]适用于XmlDCSerializer。请注意:在类上应用DataContract后,您需要将DataMember添加到要序列化的属性。

这些属性可以应用于json和xml序列化器,并且可以为模型类提供更多的控制。

参考官方解决方案:https://code.msdn.microsoft.com/Loop-Reference-handling-in-caaffaf7