来自 Newtonsoft JsonSerializer 使用 Entity Framework Core 的自引用循环

Self referencing loop from Newtonsoft JsonSerializer using Entity Framework Core

我遇到了错误:

JsonSerializationException: Self referencing loop detected for property 'Subject' with type 'Project.Models.Subject'. Path 'data[0].Totals'.

当我加载一个包含由 IEnumerable<Subject> 模型填充的 dataGrid 的视图时,会发生这种情况。网格是绑定到视图模型的 DevExtreme DataGrid,如下所示:

@(Html.DevExtreme().DataGrid()
    .DataSource(Model)
    .Paging(paging =>
    {
        paging.Enabled(true);
        paging.PageIndex(0);
        paging.PageSize(20);
    })
    .Columns(columns =>
    {
        columns.Add().DataField("SubjectId");
        ... other fields
    })
)

它是从使用此功能从存储库中提取数据的控制器填充的:

public async Task<IEnumerable<Subject>> GetSubjectsAsync()
        {
            return await _context.Subject.ToListAsync();
        }

主题 table 与总计具有 1:1 关系,总计具有对主题的外键引用。项目中的模型如下所示(从 Scaffold-DbContext 生成):

public partial class Subject
    {
        public Guid SubjectId { get; set; }
        public virtual Totals Totals { get; set; }
    }

public partial class Totals
    {
        public Guid TotalsId { get; set; }
        public virtual Subject Subject { get; set; }
    }

由于这 2 个对象相互引用,因此在序列化时会导致循环。为了纠正这个问题,我将这个配置添加到我的 Startup.ConfigureServices 方法中:

services.AddMvc()
                .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

我从这个答案中得到的:

然而,这并不能解决问题,当我加载涉及主题的视图时,它仍然会导致错误。将 [JsonIgnore] 添加到 Totals 的主题 属性 可以解决问题,但我不想将其添加到我模型中的每个子项 属性 并且每次更新时都必须重做我的数据库模型。

JSON 序列化期间 self-referencing 循环的问题与 EFCore 如何加载相关数据 (docs) 有关。当您加载集合时,相关实体可能会自动填充,也可能不会自动填充,具体取决于之前是否已加载这些对象。它被称为 自动 fix-up 导航属性 。或者,可以通过 .Include().

急切加载它们

每当您获得要序列化的 self-referencing 个实体图时,有几个选项:

  • Newtonsoft.Json.ReferenceLoopHandling.Ignore (officially recommended)。它可以工作,但如果 self-reference 发生在层次结构的深处,仍然会导致序列化过多的数据。

  • [JsonIgnore] 导航属性上的属性。如您所述,重新生成模型 类 时属性会消失。因此,它们的使用可能会很不方便。

  • (最佳选择) Pre-select 属性子集:

    var minimallyNecessarySet= _nwind.Products.Select(p => new {
        p.ProductID,
        p.ProductName,
        p.UnitPrice,
        p.CategoryID
    });
    
    return minimallyNecessarySet.ToList();
    

    这种方法的优点是只序列化需要的数据。它兼容 DevExtreme 的 DataSourceLoader:

    return DataSourceLoader.Load(minimallyNecessarySet, loadOptions);