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

.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:禁用延迟加载并限制加载层级

  1. 移除实体类中导航属性的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; }
}
  1. 控制器查询时仅加载需要的层级:
[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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 23:10:55