我有 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)
*
我已经在一个新项目中尝试了你的代码,在首先为 3.0 安装包 Microsoft.AspNetCore.Mvc.NewtonsoftJson 后,第二种方法似乎效果很好
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
尝试一个新项目并比较差异。
.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
);
谁仍然面临这个问题:检查您是否await
- 编辑了所有异步方法。
JsonIgnore
之后有人来这里,您需要使所有内容异步.. 您的服务和您的控制器中的功能!
更新:
使用 .NET 6
,System.Text.Json
可以选择忽略循环引用,如下所示:
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
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
资源:
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,
}));
});
System.Text.Json
ReferenceHandler.IgnoreCycles
vs Newtonsoft.Json.ReferenceLoopHandling.Ignore
的示例来澄清差异。
在启动时设置 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;}
}
这使用 System.Text.Json
var options = new JsonSerializerOptions()
{
MaxDepth = 0,
IgnoreNullValues = true,
IgnoreReadOnlyProperties = true
};
使用选项序列化
objstr = JsonSerializer.Serialize(obj,options);
我没有使用 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;
});
经过数小时的调试,它确实有一个简单的解决方案。我发现 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
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
);
因为首先我们需要将属性添加到我们可能具有循环引用的所有模型中。
当我在控制器方法中错误地返回 Task<object>
而不是 object
时,我收到了这样的错误。该任务导致一个循环。检查您要返回的内容。
发生这种情况是因为当涉及到 JSON 序列化时,您的数据模型之间存在 2 种方式的关系。
您不应该直接返回您的数据模型。将其映射到新的响应模型,然后将其返回。
对于未发现其他解决方案有效的其他人,您实际上需要分析您的完整调用堆栈,并查看是否有任何 async
调用未在预期等待的位置await
。这是问题中提到的实际问题。
例如,考虑 MyAppService
中的以下方法,它调用 MyOtherService
的 async Task<int>
:
public async Task<int> Create(InputModel input)
{
var id = _myOtherService.CreateAndGetIdAsync(input);
return Created("someUri", id);
}
如果 CreateAndGetIdAsync
方法是 async Task
,则对上述 Create 方法的调用将通过所讨论的给定异常。这是因为序列化会中断,因为 id
是 Task<int>
但实际上不是 int
。因此,在返回响应之前必须await
。
附加说明:这里还要注意一件事,即使出现此异常,它也不会影响数据库操作。即,在我上面的示例中,db 操作将成功。同样,如 OP 中所述,我使用的 ORM 并未引发异常,但该异常随后按调用堆栈的顺序(在调用者之一中)引发。
正如@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。
我知道这个问题是针对 .net Core 3.0 的,但是对于在 .net 5.0 中遇到相同问题的任何人,我找到了解决方案 here。
总之,需要在 Startup 类的 ConfigureServices 方法中添加以下代码:
services.AddControllers().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);
我遇到了这个问题,我很困惑,因为我有另一个运行相同代码的应用程序,唯一的区别是这次我想使用 await,而在最后一个应用程序中我使用了 ConfigureAwait(false).GetAwaiter().GetResult();
因此,通过在 Async 方法的末尾删除 await 并添加 ConfigureAwait(false).GetAwaiter().GetResult()
,我能够解决这个问题。
我遇到了这个问题,我不得不告诉应用程序/上下文忽略子实体上的父实体,方法是将以下内容添加到我的数据库上下文类中的 OnModelCreating(ModelBuilder builder) 方法中:
builder.Entity<ChildEntity>()
.HasOne(a => a.ParentEntity)
.WithMany(m => m.ChildEntities);
builder.Entity<ChildEntity>().Ignore(a => a.ParentEntity);
忽略的最后一行是为我做的。
MinimalAPI 使用这个:
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
接受的答案将默认序列化程序从 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
}
}
]
}
}
不定期副业成功案例分享
ReferenceHandler.Preserve
,.NET 6 包含ReferenceHandler.IgnoreCycles
;两者都是避免切换回 Newtonsoft.Json 并保持 System.Text.Json 的性能提升的非常好的选择。