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
相关产品推荐
相关产品推荐

