【问题标题】:How can I avoid circular reference errors when serializing EF Core results?序列化 EF Core 结果时如何避免循环引用错误?
【发布时间】:2021-08-26 12:34:12
【问题描述】:

我在我的 EF Core 项目中设置了以下实体模型:

public class ForumPost
{
    [Key]
    public int Id { get; set; }
    public string Title { get; set; }
    // ...some other properties
    public List<Tag> Tags { get; set; }
}

public class Tag
{
    [Key]
    public string Name { get; set; }
    public string Description { get; set; }
    // ...some other properties
    public List<ForumPost> Posts { get; set; }
}

所以我在帖子和标签之间有多对多的关系。我的OnModelCreating没有关系配置。

我的上下文设置如下:

public class ForumsContext : DbContext
{
    public DbSet<ForumPost> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }
}

当我如下查询我的ForumsContext 时:

 var posts = await context.Posts.Include(p => p.Tags).ToListAsync();

EF 为posts 中每个ForumPost 对象中的每个Tag 对象自动填充Posts 字段。我无法弄清楚为什么会发生这种情况,如果我要将上面的 posts 列表序列化为 JSON,它会由于对象循环 (Post-&gt;Tags-&gt;Posts-&gt;Post) 而引发异常。

为什么要填充标签的帖子列表?如何告诉 EF 不要填充此列表。

【问题讨论】:

  • 所以问题与 EF Core 完全无关,它与 JSON 和循环引用有关。您使用的是哪个 JSON 库? JSON.NET? System.Text.Json?您可以找到询问如何处理任一循环引用的问题
  • 一个非常非常好的建议是不要使用您的数据模型作为您的响应模型。除了非常简单的情况外,每一个都需要不同的关系,不同的领域。您可能不想在任何 JSON 模型中显示某些字段,因为它们是噪音(例如 Created 等审计字段)或敏感字段(例如信用卡号)
  • 您使用的是哪个 .NET Core 版本? System.Text.Json 可以handle circular references in .NET 5 and later。您还可以将[JsonIgnore] 属性添加到Tag.Posts

标签: c# json .net entity-framework-core


【解决方案1】:

这就是实体框架的工作原理。 Include 调用会填充关系两侧的导航属性。

实体框架约定规定您必须在多对多关系的两端都有导航属性。

使用 fluent API 似乎也不可能只有一个导航属性。下面的代码不起作用,并询问是否有办法避免为其中一个实体定义属性,在这个线程the team answered with "Not yet"

modelBuilder.Entity<ForumPost>().HasMany<Tag>(post => post.Tags).WithMany();

JSON 对象循环

关于序列化为 JSON 时的对象循环错误,these can be handled or ignored in .NET 5 or later.

This article 更详细地介绍了该问题,并展示了 NewtonSoft.Json 的解决方案。

我不喜欢这种解决方案。您最终会得到null 属性,其中序列化程序因遇到循环而停止。

但所有这些都是无关紧要的。您无需更改数据模型或 JSON 序列化程序设置即可解决此问题。问题是您首先要公开您的数据模型。

建议:使用响应模型

正确的解决方案是将您的实体映射到响应/视图模型或 DTO。这使您可以格式化正确的响应,并允许您的数据模型在不干扰响应模型的情况下发展。

例子:

public class ForumPostResponse
{
    public int Id { get; set; }

    public string Title { get; set; }

    public List<Tag> Tags { get; set; }
}

public class TagResponse
{
    public string Name { get; set; }

    public string Description { get; set; }
}

您也可以将ForumPostResponse.Tags 格式化为string 的列表,以防您只需要论坛帖子上的标签名称列表,并在其他回复中包含描述。一切皆有可能。

您确实需要编写额外的代码来处理对象之间的映射,但为了更好和更易于维护的设计,这是一个小的折衷。此外,还有几个库可以简化流程,最受欢迎的是AutoMapper

【讨论】:

【解决方案2】:

我知道这可能不是序列化循环引用的最佳解决方案,但一种解决方法是初始化列表属性:

public class ForumPost
{
    [Key]
    public int Id { get; set; }
    public string Title { get; set; }
    // ...some other properties
    public List<Tag> Tags { get; set; } = new List<Tag>();
}

public class Tag
{
    [Key]
    public string Name { get; set; }
    public string Description { get; set; }
    // ...some other properties
    public List<ForumPost> Posts { get; set; } = new List<ForumPost>();
}

此外,由于初始化,您可以操作这些列表而不必担心空异常和所有这些东西。但请注意,这个问题可能有更好的解决方案。

【讨论】:

  • 我认为这并不能解决序列化问题,但它确实是避免空引用异常的好方法。
  • 你可以试试,它确实取消了序列化的循环引用
猜你喜欢
  • 1970-01-01
  • 2011-05-25
  • 1970-01-01
  • 1970-01-01
  • 2017-06-24
  • 2016-11-21
  • 1970-01-01
  • 1970-01-01
  • 2015-09-05
相关资源
最近更新 更多