避免或控制 Entity Framework Core 中的循环引用

use*_*731 6 c# asp.net-mvc entity-framework-core asp.net-core

我真的受够了,但至少我想知道发生了什么事。开始了:

我的项目是一个 ASP.NET Core Web 应用程序,具有 Code First Entity Framework Core 和 Angular 前端。

我想控制何时加载引用的对象。它们可能很有用,但它们也可能在前端创建带有内部错误的循环引用。(JSON 将无限长。)

楷模:

class Book {
   public virtual ICollection<Page> Pages { get; set; }
   ...simple properties
}

class Page {
   public virtual Book Book { get; set; }
   ...simple properties
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,书籍中的每本书都会有一个空/空页面列表。

using (var context = new MoneyStatsContext())
{
   var books = context.Books.Where(rule => rule.State == 1).ToList();
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,Pages列表不为 null,并且每个Page都会设置其Book属性。从而创建循环引用。

using (var context = new MoneyStatsContext())
{
   var books = context.Books.Where(rule => rule.State == 1).Include(x => x.Pages).ToList();
}
Run Code Online (Sandbox Code Playgroud)

如何避免循环引用?除了创建新模型并手动指定每个属性之外,我真的没有其他(更简单的)选择吗?

.Select(new Book() {
   ...setting each property by hand
}
Run Code Online (Sandbox Code Playgroud)

我发现的不起作用的解决方案:

  • 尝试将其设置为 false 和 true。似乎没有改变任何事情。
public MyContext()
{
   this.ChangeTracker.LazyLoadingEnabled = false;
}
Run Code Online (Sandbox Code Playgroud)
  • 尝试在 Startup.cs 中指定这一点,但选项没有 SerializerSettings 属性。
services.AddMvc().AddJsonOptions(options =>
{
   options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Run Code Online (Sandbox Code Playgroud)

任何帮助,将不胜感激。提前致谢。

Gur*_*ron 17

假设您正在使用最新最好的 ASP .NET Core 3.1System.Text.Json来处理引用循环,您将需要切换到“返回” Newtonsoft.Json(尽管值得一提的是,这System.Text.Json应该更快。对引用循环处理的支持也即将推出,如@Eric) J.在评论中写道):

services.AddControllers()
    .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore)
Run Code Online (Sandbox Code Playgroud)

至于 EF 创建引用循环 - 它被调用relationship fixup,并且您不能对此做很多事情(请参阅答案)。AsNoTracking可以提供一点帮助(但不是在这种情况下Include)。

我个人的方法是从端点返回 DTO,而不是直接从实体返回。

UPD

在 .NET 5.0 中ReferenceHandler引入了,所以接下来应该做到这一点:

services.AddControllersWithViews()
    .AddJsonOptions(options =>
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve)
Run Code Online (Sandbox Code Playgroud)