如何在Entity Framework中禁用自动包含以解决对象循环问题
当执行这段EF Core查询时:
_dbContext.Employees .Include(e => e.Company) .Where(e => e.IsDelete == false)
因为Employee和Company之间是双向导航(Employee关联Company,Company又关联ICollection<Employee>),返回的实体形成无限循环嵌套,序列化时直接触发错误:
A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee.Company.Employee
如果不想用JsonConvert的ReferenceLoopHandling.Ignore方案,试试这些EF层面的解决办法:
1. 用投影(Projection)返回需要的字段
直接在查询里指定要返回的数据,不加载完整的双向导航实体,从根源避免循环:
var result = _dbContext.Employees .Where(e => e.IsDelete == false) .Select(e => new { Id = e.Id, Name = e.Name, Company = new { Id = e.Company.Id, CompanyName = e.Company.Name } }) .ToList();
要是需要强类型,就定义DTO(数据传输对象)代替匿名类:
// 定义DTO public class EmployeeDto { public int Id { get; set; } public string Name { get; set; } public CompanyDto Company { get; set; } } public class CompanyDto { public int Id { get; set; } public string Name { get; set; } // 不包含Employees属性 } // 查询代码 var result = _dbContext.Employees .Where(e => e.IsDelete == false) .Select(e => new EmployeeDto { Id = e.Id, Name = e.Name, Company = new CompanyDto { Id = e.Company.Id, Name = e.Company.Name } }) .ToList();
这种方式最稳妥,完全切断循环引用,还能减少数据传输量。
2. 禁用延迟加载
如果不需要反向导航的延迟加载,直接全局或实体级禁用:
- 全局禁用:在DbContext的
OnConfiguring里关闭延迟加载代理:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("你的连接字符串") .UseLazyLoadingProxies(false); }
或者确保实体的导航属性不是virtual(EF默认对virtual属性启用延迟加载)。
- 实体级禁用:在模型构建时,配置不加载Company的Employees导航:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Company>() .HasMany(c => c.Employees) .WithOne(e => e.Company) .HasForeignKey(e => e.CompanyId) .Metadata.DependentToPrincipal.SetIsEagerLoaded(false); }
3. 用AsNoTracking()配合选择性加载
如果不需要跟踪实体状态,加上AsNoTracking(),但核心还是要避免加载反向导航属性。不过相比投影,这个方式不够彻底,还是推荐用投影。
4. 在实体上标记忽略反向导航的序列化
虽然这算序列化层面的配置,但如果是在实体类上标记,也算从数据模型出发的解决方式:
public class Company { public int Id { get; set; } public string Name { get; set; } [JsonIgnore] public ICollection<Employee> Employees { get; set; } }
这样序列化时会自动忽略Company的Employees属性,避免循环。
内容的提问来源于stack exchange,提问作者Anton Moiseev

