ChatGPT解决这个技术问题 Extra ChatGPT

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

我有 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 maxDepth) 在 System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer , Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancelToken) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter。在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker 调用程序,任务 lastTask, State next, Sco pe scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() 的状态,布尔值& isCompleted)

*

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

R
Ryan

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

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

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


这里的关键时刻是重新安装正确版本的 Microsoft.AspNetCore.Mvc.NewtonsoftJson 我没有注意版本,因为该软件包在框下可用,没有任何错误和警告!感谢您的回答!一切都按我的预期工作!
改进了系统json的性能,我们必须使用NewtonsoftJson,这不是错了吗? :/
@MarekUrbanovicz 调用无法处理周期或字典类型生产就绪的序列化程序是错误的。
从 .NET 5 System.Text.Json 开始包含选项 ReferenceHandler.Preserve,.NET 6 包含 ReferenceHandler.IgnoreCycles;两者都是避免切换回 Newtonsoft.Json 并保持 System.Text.Json 的性能提升的非常好的选择。
+1 @Jozkee 的评论。我来这里是从 .NET 5 实现中寻找这个,我们试图避免使用 Newtonsoft.Json 包并保留新的 System.Text.Json。解决了我的问题,谢谢!老实说,这应该是答案之一。
g
granadaCoder

.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/…
这就是我需要的
M
Mitulát báti

谁仍然面临这个问题:检查您是否await - 编辑了所有异步方法。


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

更新:

使用 .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.Json ReferenceHandler.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:\n{tylerJson}");

            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 maxDepth)

直接从 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.IgnoreCycles vs Newtonsoft.Json.ReferenceLoopHandling.Ignore 的示例来澄清差异。
非常感谢您。你节省了我很多时间。
t
timur

在启动时设置 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 );
I
IObote

这使用 System.Text.Json

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

使用选项序列化

objstr = JsonSerializer.Serialize(obj,options);

这会设置 MaxDepth,但仍然不允许深度大于 0 的对象通过。我们需要的是忽略
A
Abishek G.C.

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

对于 .Net Core 3.1

在 Startup.cs

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

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

对于.Net 6

在 Program.cs 中

builder.Services.AddControllers().AddJsonOptions(options => 
{ 
    options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
    options.JsonSerializerOptions.WriteIndented = true;
});

P
Pang

经过数小时的调试,它确实有一个简单的解决方案。我发现 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


N
Nantharupan
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
);

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


Y
Yury Yatskov

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


A
A Houghton

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

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


问题清楚地表明“我不想创建单独的模型”。
这通常是我的原因
Z
Zeeshan

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

例如,考虑 MyAppService 中的以下方法,它调用 MyOtherServiceasync Task<int>

public async Task<int> Create(InputModel input)
{
    var id = _myOtherService.CreateAndGetIdAsync(input);
    return Created("someUri", id);
}

如果 CreateAndGetIdAsync 方法是 async Task,则对上述 Create 方法的调用将通过所讨论的给定异常。这是因为序列化会中断,因为 idTask<int> 但实际上不是 int。因此,在返回响应之前必须await

附加说明:这里还要注意一件事,即使出现此异常,它也不会影响数据库操作。即,在我上面的示例中,db 操作将成功。同样,如 OP 中所述,我使用的 ORM 并未引发异常,但该异常随后按调用堆栈的顺序(在调用者之一中)引发。


V
Vicktor

正如@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 中的这一行解决了这个问题。谢谢你。
H
Hugo Nava Kopp

我知道这个问题是针对 .net Core 3.0 的,但是对于在 .net 5.0 中遇到相同问题的任何人,我找到了解决方案 here

总之,需要在 Startup 类的 ConfigureServices 方法中添加以下代码:

services.AddControllers().AddJsonOptions(x =>
   x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);

M
MrLu

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

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


这与问题无关。也许您发布了错误的问题?
我得到了同样的错误“检测到不支持的可能的对象循环”,但也许我的问题与这个不同。
H
How 'bout a Fresca

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

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

忽略的最后一行是为我做的。


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

MinimalAPI 使用这个:

builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
    options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});

L
Lukáš Kmoch

接受的答案将默认序列化程序从 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
            }
         }
      ]
   }
}

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅