You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core关联数据仅返回唯一实例及JSON $id字段问题咨询

问题原因及解决方案

问题原因

这是EF Core变更跟踪机制与JSON序列化器默认行为共同导致的:

  • EF Core的变更跟踪会保证同一个上下文内,主键相同的实体只会被实例化一次。当你用Include加载关联数据时,多个Blog关联同一个Author的情况下,EF Core只会创建一个Author实例,后续所有关联该Author的Blog都会复用这个实例。
  • 你的JSON序列化器(不管是ASP.NET Core默认的System.Text.Json还是Newtonsoft.Json)默认开启了引用处理,当检测到重复引用的对象时,会用$id标记首次出现的实例,后续用$ref引用它,既避免重复序列化冗余数据,也能防止潜在的循环引用报错。

解决方案

1. 关闭EF Core变更跟踪(快速临时解决)

在查询时添加AsNoTracking(),让EF Core不再跟踪实体实例,这样每个Blog的Author都会生成独立的新实例,序列化时就不会出现$ref:

public async Task<List<Blog>> GetAllBlogs(BlogContext context) 
{
    return await context.Blogs.Include(a => a.Author).AsNoTracking().ToListAsync();
}

注:把同步的ToList()改成异步的ToListAsync(),更符合方法的async语义。

2. 配置JSON序列化器禁用引用处理

如果需要保留EF Core的跟踪功能,可以修改序列化器配置,关闭引用识别:

  • 使用System.Text.Json(ASP.NET Core默认):
    在Program.cs中添加配置:
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        // 忽略循环引用,同时禁用$id/$ref生成
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
    });
  • 使用Newtonsoft.Json:
    在Program.cs中添加配置:
builder.Services.AddControllers()
    .AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
    });

3. 使用DTO投影(生产环境推荐方案)

创建专门的数据传输对象(DTO),只返回业务需要的字段,从根源上避免实体引用问题,同时符合分层架构的最佳实践:
首先定义DTO类:

public class BlogDto
{
    public string BlogId { get; set; }
    public string BlogTitle { get; set; }
    public string BlogContent { get; set; }
    public string? BlogTags { get; set; }
    public AuthorDto Author { get; set; }
}

public class AuthorDto
{
    public string AuthorId { get; set; }
    public string AuthorFirstName { get; set; }
    public string? AuthorMiddleName { get; set; }
    public string AuthorLastName { get; set; }
    public string? BriefDescription { get; set; }
}

然后修改查询方法,用Select将实体投影到DTO:

public async Task<List<BlogDto>> GetAllBlogs(BlogContext context) 
{
    return await context.Blogs
        .Include(a => a.Author)
        .Select(blog => new BlogDto
        {
            BlogId = blog.BlogId,
            BlogTitle = blog.BlogTitle,
            BlogContent = blog.BlogContent,
            BlogTags = blog.BlogTags,
            Author = new AuthorDto
            {
                AuthorId = blog.Author.AuthorId,
                AuthorFirstName = blog.Author.AuthorFirstName,
                AuthorMiddleName = blog.Author.AuthorMiddleName,
                AuthorLastName = blog.Author.AuthorLastName,
                BriefDescription = blog.Author.BriefDescription
            }
        })
        .ToListAsync();
}

这种方式不仅解决了序列化的引用问题,还能避免暴露实体中不必要的字段,提升接口安全性和传输性能。

内容的提问来源于stack exchange,提问作者bzbz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:17:42