检测到.Net Core 3.0 可能的对象循环,但不支持

Posted

技术标签:

【中文标题】检测到.Net Core 3.0 可能的对象循环,但不支持【英文标题】:.Net Core 3.0 possible object cycle was detected which is not supported 【发布时间】:2020-03-30 15:32:12 【问题描述】:

我有 2 个实体,它们是一对多相关的

public class Restaurant 
   public int RestaurantId get;set;
   public string Name get;set;
   public List<Reservation> Reservations get;set;
   ...

public class Reservation
   public int ReservationId get;set;
   public int RestaurantId get;set;
   public Restaurant Restaurant get;set;

如果我尝试使用我的 api 预订餐厅

   var restaurants =  await _dbContext.Restaurants
                .AsNoTracking()
                .AsQueryable()
                .Include(m => m.Reservations).ToListAsync();
    .....

我收到错误响应,因为对象包含对彼此的引用。 有相关帖子推荐to create separate model 或添加NewtonsoftJson configuration

问题是我不想创建单独的模型,第二个建议没有帮助。 有没有办法在没有循环关系的情况下加载数据? *

System.Text.Json.JsonException:检测到可能的对象循环 不支持。这可能是由于一个周期或如果 对象深度大于最大允许深度 32。在 System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected(Int32 最大深度)在 System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions 选项,WriteStack& 状态)在 System.Text.Json.JsonSerializer.WriteAsyncCore(流 utf8Json,对象 值,类型 inputType,JsonSerializerOptions 选项, CancellationToken 取消令牌)在 Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext 上下文,编码选择编码)在 Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext 上下文,编码选择编码)在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker 调用者,任务 lastTask,下一个状态,作用域范围,对象状态,布尔值 已完成)在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed 上下文)在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& 范围, Object& 状态, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()

*

【问题讨论】:

要求它忽略 Reservation 类的 Restaurant 属性。 真的,您不应该直接从您的 API 返回您的数据库实体。我建议创建特定于 API 的 DTO 并相应地映射。当然,您说过您不想这样做,但我认为将 API 和持久性内部分开是一般的好习惯。 “问题是我不想创建单独的模型”。除非您这样做,否则您的设计从根本上是有缺陷的。 API 是一个类似于接口的契约(它实际上是一个应用程序编程接口)。一旦发布,它就不应该改变,任何改变都需要一个新版本,新版本需要与旧版本同时运行(将来会被弃用并最终删除)。这让客户有时间更新他们的实现。如果您直接返回一个实体,那么您将紧密耦合您的数据层。 对该数据层的任何更改都需要立即且不可逆转地更改 API,立即中断所有客户端,直到它们更新其实现。如果它不明显,那是一件坏事。简而言之:永远不要从 API 接受或返回实体。您应该始终使用 DTO。 "您应该始终使用 DTO" ...好吧,不,并非总是如此。如果您的 API 的唯一订阅者是您的 SPA 客户端应用程序 - 如果没有什么可以阻止的,我为什么要浪费时间编写 DTO,因为唯一的订阅者也必须立即更新。最佳做法很好,但我不喜欢“总是”。 【参考方案1】:

我已经在一个新项目中尝试了你的代码,在首先为 3.0 安装包 Microsoft.AspNetCore.Mvc.NewtonsoftJson 后,第二种方法似乎运行良好

services.AddControllersWithViews()
    .AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

尝试一个新项目并比较差异。

【讨论】:

这里的关键时刻是重新安装正确版本的 Microsoft.AspNetCore.Mvc.NewtonsoftJson 我没有注意版本,因为该软件包在框下可用,没有任何错误和警告!感谢您的回答!一切都和我预期的一样! 提高系统json的perf是不是就得用NewtonsoftJson了? :// 谢谢@Ryan :) @MarekUrbanowicz 调用无法处理周期或字典类型生产就绪的序列化程序是错误的。 从 .NET 5 开始 System.Text.Json 包含选项 ReferenceHandler.Preserve 和 .NET 6 包含 ReferenceHandler.IgnoreCycles;两者都是避免切换回 Newtonsoft.Json 并保持 System.Text.Json 的性能提升的非常好的选择。【参考方案2】:

.NET Core 3.1 安装包 Microsoft.AspNetCore.Mvc.NewtonsoftJson(来自https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/)

Startup.cs添加服务

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

【讨论】:

更多详情请查看:thecodebuzz.com/… 这就是我需要的【参考方案3】:

谁仍然面临这个问题:检查你是否await-ed 所有异步方法。

【讨论】:

接受的响应可能是有效的,但正确的响应应该是这个。不等待任务的事实会产生暴露的错误。 这也是正确的,因为如果有人在添加JsonIgnore 之后来到这里,您需要使一切异步.. 您的服务和您的控制器中的功能! 谢谢。实际上,我确实放下了所有的 POST 堆栈,一切都在等待。但是后来注意到控制器中缺少一个,在 POST 之后我返回 Get 以获取完整数据,并且没有等待 Get 调用。现在没有收到错误。 把我从疯狂中救了出来。在我的控制器中忘记了等待 非常感谢.. :) .. 我故意离开异步,以为什么都不会发生..& 我浪费了 2 个小时的调试时间【参考方案4】:

在启动时设置 JSON 序列化选项可能是一种首选方式,因为您将来可能会遇到类似情况。但与此同时,您可以尝试向模型添加数据属性,这样它就不会被序列化:https://www.newtonsoft.com/json/help/html/PropertyJsonIgnore.htm

public class Reservation 
    public int ReservationId get;set; 
    public int RestaurantId get;set; 
    [JsonIgnore]
    public Restaurant Restaurant get;set; 

【讨论】:

这也有效。但是正如您所提到的,您必须更新所有模型,我更喜欢 services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore );【参考方案5】:

这使用 System.Text.Json 工作

var options = new JsonSerializerOptions()
        
            MaxDepth = 0,
            IgnoreNullValues = true,
            IgnoreReadOnlyProperties = true
        ;

使用选项序列化

objstr = JsonSerializer.Serialize(obj,options);

【讨论】:

这会设置 MaxDepth 但仍然不允许深度大于 0 的对象通过。我们需要的是忽略【参考方案6】:

更新:

使用.NET 6System.Text.Json 可以选择忽略这样的循环引用:

JsonSerializerOptions options = new()

    ReferenceHandler = ReferenceHandler.IgnoreCycles,
    WriteIndented = true
;

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references?pivots=dotnet-6-0#ignore-circular-references

ReferenceHandler.Preserve 的问题在于 JSON 密钥以 $ 为前缀,这可能会导致一些问题。

例如System.Text.JsonReferenceHandler.IgnoreCycles

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SerializeIgnoreCycles

    public class Employee
    
        public string Name  get; set; 
        public Employee Manager  get; set; 
        public List<Employee> DirectReports  get; set; 
    

    public class Program
    
        public static void Main()
        
            Employee tyler = new()
            
                Name = "Tyler Stein"
            ;

            Employee adrian = new()
            
                Name = "Adrian King"
            ;

            tyler.DirectReports = new List<Employee>  adrian ;
            adrian.Manager = tyler;

            JsonSerializerOptions options = new()
            
                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                WriteIndented = true
            ;

            string tylerJson = JsonSerializer.Serialize(tyler, options);
            Console.WriteLine($"Tyler serialized:\ntylerJson");

            Employee tylerDeserialized =
                JsonSerializer.Deserialize<Employee>(tylerJson, options);

            Console.WriteLine(
                "Tyler is manager of Tyler's first direct report: ");
            Console.WriteLine(
                tylerDeserialized.DirectReports[0].Manager == tylerDeserialized);
        
    


// Produces output like the following example:
//
//Tyler serialized:
//
//  "Name": "Tyler Stein",
//  "Manager": null,
//  "DirectReports": [
//    
//      "Name": "Adrian King",
//      "Manager": null,
//      "DirectReports": null
//    
//  ]
//
//Tyler is manager of Tyler's first direct report:
//False

来源:

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references?pivots=dotnet-6-0#ignore-circular-references

Newtonsoft.Json.ReferenceLoopHandling.Ignore 为例

public class Employee

    public string Name  get; set; 
    public Employee Manager  get; set; 


Employee joe = new Employee  Name = "Joe User" ;
Employee mike = new Employee  Name = "Mike Manager" ;
joe.Manager = mike;
mike.Manager = mike;

string json = JsonConvert.SerializeObject(joe, Formatting.Indented, new JsonSerializerSettings

    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);

Console.WriteLine(json);
// 
//   "Name": "Joe User",
//   "Manager": 
//     "Name": "Mike Manager"
//   
// 

https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm

原文:

我在使用 API Controller with actions, using entity framework 创建的控制器中的默认 POST 方法中收到此错误。

return CreatedAtAction("GetLearningObjective", new  id = learningObjective.Id , learningObjective);

System.Text.Json.JsonException:检测到可能的对象循环。 这可能是由于循环或物体深度较大 超过最大允许深度 32。考虑使用 JsonSerializerOptions 上的 ReferenceHandler.Preserve 以支持循环。 在 System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 最大深度)

当直接从 Postman 或浏览器调用 HttpGet 时,它可以正常工作。通过像这样编辑Startup.cs - services.AddControllers() 解决:

services.AddControllers().AddJsonOptions(options =>

    options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
);

你也可以这样解决:

services.AddControllers(options =>

    options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
    options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonSerializerOptions(JsonSerializerDefaults.Web)
    
        ReferenceHandler = ReferenceHandler.Preserve,
    ));
);

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references?pivots=dotnet-5-0

【讨论】:

忽略这一点并不能解决问题。你只是忽略了这个问题。 @MuhammedYILMAZ 我绝对会说在许多情况下将循环引用属性设置为 null 是一种解决方案。我确实添加了一个 System.Text.Json ReferenceHandler.IgnoreCyclesNewtonsoft.Json.ReferenceLoopHandling.Ignore 的示例,以澄清差异。 非常感谢。你节省了我很多时间。【参考方案7】:

经过几个小时的调试,它确实有一个简单的解决方案。我发现this link 很有帮助。

这个错误是因为:

在 ASP.NET Core 3.0 及以上版本中使用的默认 JSON 序列化器。

ASP.NET Core 3.0 移除了对 JSON.NET 的依赖,并使用了它自己的 JSON 序列化器,即“System.Text.Json”。

我能够解决添加对 NewtonsoftJson Nuget 包的引用的问题,

PM> Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.1.2

并如下更新 Startup.cs,

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

System.Text.Json 序列化程序目前不支持ReferenceLoopHandling

【讨论】:

【参考方案8】:
public class Reservation 
public int ReservationId get;set; 
public int RestaurantId get;set; 
[JsonIgnore]
public Restaurant Restaurant get;set; 

上述方法也有效。但我更喜欢以下

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

因为首先我们需要将属性添加到我们可能具有循环引用的所有模型中。

【讨论】:

【参考方案9】:

当我在控制器方法中错误地返回 Task&lt;object&gt; 而不是 object 时,出现了这样的错误。该任务导致一个循环。检查您要返回的内容。

【讨论】:

【参考方案10】:

发生这种情况是因为当涉及到 JSON 序列化时,您的数据模型之间存在 2 路关系。

您不应该直接返回您的数据模型。将其映射到新的响应模型,然后将其返回。

【讨论】:

问题明确指出“我不想创建单独的模型”。【参考方案11】:

对于没有发现其他解决方案有效的其他人,您实际上需要分析您的完整调用堆栈,并查看是否有任何 async 调用不是 awaited 预期等待的地方。这是问题中提到的实际问题。

例如,考虑MyAppService中的以下方法,它调用MyOtherServiceasync Task&lt;int&gt;

public async Task<int> Create(InputModel input)

    var id = _myOtherService.CreateAndGetIdAsync(input);
    return Created("someUri", id);

如果CreateAndGetIdAsync 方法是async Task,则上面对这个 Create 方法的调用将通过问题中提到的给定异常。这是因为序列化会中断,因为idTask&lt;int&gt;,但实际上不是int。所以在回复之前必须await

补充说明: 在这里还要注意一件事很重要,即使出现了这个异常,它也不会影响 db 操作。即,在我上面的示例中,db 操作将成功。同样,正如 OP 中所提到的,我使用的 ORM 并没有抛出异常,但是这个异常后来在调用堆栈的序列中(在调用者之一中)抛出。

【讨论】:

【参考方案12】:

正如@Jozkee 在已接受答案的评论中所述,.NET 6 在System.Text.Json 中包含ReferenceHandler.IgnoreCycles

这就是我在不安装 Newtonsoft.Json 并通过将以下内容添加到 Program.cs 来利用 .NET 6 的新增功能的情况下解决此问题的方法。


builder.Services.AddControllersWithViews()
    .AddJsonOptions(options => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);

这里有一个helpful article on object cycles,供任何不确定自己是什么的人使用。

【讨论】:

服务器上 startup.cs 中的这一行解决了这个问题。谢谢。【参考方案13】:

我没有使用 NewtonsoftJson,而是使用 System.Text.Json.Serialization

在 Startup.cs 中

 public void ConfigureServices(IServiceCollection services)
 
    ..........
    .......

    services.AddControllers().AddJsonOptions(options =>
    
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        options.JsonSerializerOptions.WriteIndented = true;
    );
 

【讨论】:

【参考方案14】:

我遇到了这个问题,我很困惑,因为我有另一个应用程序运行相同的代码,唯一的区别是这次我想使用 await,而在上一个应用程序中我使用了 ConfigureAwait(false).GetAwaiter().GetResult();

因此,通过在 Async 方法的末尾删除 await 并添加 ConfigureAwait(false).GetAwaiter().GetResult(),我能够解决此问题。

【讨论】:

这与问题无关。也许你发错了问题? 我得到了同样的错误“检测到不支持的可能的对象循环”但也许我的问题与这个不同。【参考方案15】:

我遇到了这个问题,我必须通过将以下内容添加到我的数据库上下文类中的 OnModelCreating(ModelBuilder builder) 方法来告诉应用程序/上下文忽略子实体上的父实体: p>

builder.Entity<ChildEntity>()
    .HasOne(a => a.ParentEntity)
    .WithMany(m => m.ChildEntities);
builder.Entity<ChildEntity>().Ignore(a => a.ParentEntity);

Ignore 的最后一行是为我做的。

【讨论】:

这是一种相当大锤的方法,仅用于修复序列化中的对象循环。现在 EF 永远不会填充导航属性。 这是蛮力,但它对我有用并且没有任何不良影响。如果您房子上的窗户没有正确关闭,您可以安装一扇新窗户,或者如果您不想再在房子里安装那扇窗户,您也可以选择在窗户上加墙。根据您的情况,这两者都是完全有效的。 Downvote 似乎过分了,因为它是一种可行的解决方案,即使它不是您个人会选择的解决方案。 不是我的DV,只是评论。【参考方案16】:

接受的答案将默认序列化程序从 System.Text.Json 更改为 Newtonsoft,并将通过从序列化中删除导航属性来解决循环!

订单示例:


   "id": "d310b004-79a2-4661-2f90-08d8d25fec03"
   "orderItems": [
      
         "orderId": "d310b004-79a2-4661-2f90-08d8d25fec03",
         "orderItemId": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
         "orderItem": 
            "id": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
            "name": "My order item"
         
         // where is the reference to the order?!
      
   ]

如果您不想更改默认序列化程序或需要保留导航属性,您可以配置 System.Text.Json 序列化程序以保留引用。但要小心,因为它通过提供 $id、$ref 和 $values 属性来更改输出结构!

services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve)

订单示例:


   "$id": "1",
   "id": "d310b004-79a2-4661-2f90-08d8d25fec03"
   "orderItems": 
      $"id": "2",
      $"values": [
         
            $"id": "3",
            "orderId": "d310b004-79a2-4661-2f90-08d8d25fec03",
            "orderItemId": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
            "orderItem": 
               "id": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
               "name": "My order item"
            ,
            "order": 
               "$ref": "1" // reference to the order
            
         
      ]
   

【讨论】:

以上是关于检测到.Net Core 3.0 可能的对象循环,但不支持的主要内容,如果未能解决你的问题,请参考以下文章

.Net Core 3.0 JsonSerializer 填充现有对象

。NET Core 3.1-Ajax OnGet和使用NEW System.Text.Json返回对象

激活 Autofac .NET Core Web API 时检测到循环组件依赖项

.net core 3.0 Signalr - 08 业务实现-客户端demo

.net core 3.0 Signalr - 08 业务实现-客户端demo

这是如何使用 Entity Framework Core 和 ASP.NET Core MVC 2.2+ 和 3.0 创建数据传输对象 (DTO)