.NET Web API & EF Core关联查询遇对象循环引用异常求助
问题根源
你的实体模型存在双向导航属性:
Employee包含指向Team的导航属性Team包含指向Employee集合的导航属性
当Web API序列化Employee对象时,会递归遍历所有公共属性,形成Employee→Team→Employees→Team……的无限循环,触发JsonException。
解决方案
方案1:使用DTO(数据传输对象)推荐
创建仅包含前端所需字段的DTO类,完全避免循环引用问题,同时精准控制返回数据:
// 定义EmployeeDTO public class EmployeeDTO { public long EmployeeId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime? DateOfBirth { get; set; } public string PhoneNumber { get; set; } public string Email { get; set; } // 团队信息 public TeamDTO Team { get; set; } } public class TeamDTO { public int TeamId { get; set; } public string Name { get; set; } // 业务单元信息(满足你带出业务单元的需求) public BusinessUnitDTO BusinessUnit { get; set; } } public class BusinessUnitDTO { public int BusinessUnitId { get; set; } public string Name { get; set; } }
修改控制器代码,查询时将实体映射到DTO:
[HttpGet] public async Task<ActionResult<IEnumerable<EmployeeDTO>>> GetEmployees() { var employees = await _context.Employees .Include(e => e.Team) .ThenInclude(t => t.BusinessUnit) // 加载团队所属业务单元 .ToListAsync(); // 手动映射(也可使用AutoMapper简化映射逻辑) var employeeDTOs = employees.Select(e => new EmployeeDTO { EmployeeId = e.EmployeeId, FirstName = e.FirstName, LastName = e.LastName, DateOfBirth = e.DateOfBirth, PhoneNumber = e.PhoneNumber, Email = e.Email, Team = new TeamDTO { TeamId = e.Team.TeamId, Name = e.Team.Name, BusinessUnit = new BusinessUnitDTO { BusinessUnitId = e.Team.BusinessUnit.BusinessUnitId, Name = e.Team.BusinessUnit.Name } } }).ToList(); return employeeDTOs; }
方案2:配置Json序列化忽略循环引用
在Program.cs中配置System.Text.Json自动跳过循环引用的属性,适合快速调试:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; });
该方法无需修改实体和查询逻辑,但返回的数据中循环引用的属性会被忽略(比如Team中的Employees集合不会被序列化),长期来看不如DTO可控。
方案3:禁用延迟加载并限制加载层级
- 移除实体类中导航属性的
virtual关键字,禁用EF Core的延迟加载功能:
// 修改Team类 public class Team { [Key] public int TeamId { get; set; } public string Name { get; set; } public int BusinessUnitId { get; set; } public BusinessUnit BusinessUnit { get; set; } // 移除virtual关键字 public List<Employee>? Employees { get; set; } } // 修改BusinessUnit类 public class BusinessUnit { [Key] public int BusinessUnitId { get; set; } public string Name { get; set; } public long? DirectorEmployeeId { get; set; } // 移除virtual关键字 public List<Team>? Teams { get; set; } }
- 控制器查询时仅加载需要的层级:
[HttpGet] public async Task<ActionResult<IEnumerable<Employee>>> GetEmployees() { return await _context.Employees .Include(e => e.Team) .ThenInclude(t => t.BusinessUnit) // 加载业务单元 .ToListAsync(); }
此方法通过禁用延迟加载,避免序列化时自动加载反向导航属性,但灵活性较差,后续需求变更时需调整实体代码。
内容的提问来源于stack exchange,提问作者Frederikk Krogh
相关产品推荐
相关产品推荐

