.NET Core 3.0配置Newtonsoft.Json仍遇自引用循环错误求助
看起来你已经尝试配置ReferenceLoopHandling.Ignore但还是碰到了循环引用的问题,我来帮你分析原因和可行的解决办法:
问题根源
你在控制器中直接返回了EF的Department实体,而Department关联了Company,Company又包含Departments集合(从错误路径Departments[0]可以看出)。虽然你配置了忽略循环引用,但要么是配置没有正确生效,要么是EF加载的关联数据触发了序列化时的循环检测逻辑。
解决方案
1. 确保NewtonsoftJson配置正确生效
你的Startup.cs中同时调用了AddMvc()和AddControllers()并重复添加了NewtonsoftJson配置,这可能导致配置冲突或未正确应用。ASP.NET Core 3.0+中AddControllers()已经包含了MVC核心功能,建议合并配置:
替换原来的两段配置:
services.AddMvc(option => option.EnableEndpointRouting = false) .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); services.AddControllers() .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
改为:
services.AddControllers(opt => opt.EnableEndpointRouting = false) .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore );
2. 启用PreserveReferencesHandling(快速临时方案)
如果上面的配置仍不生效,可以尝试让NewtonsoftJson主动处理循环引用而非忽略,通过添加PreserveReferencesHandling:
services.AddControllers(opt => opt.EnableEndpointRouting = false) .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddNewtonsoftJson(x => { x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; x.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; });
这种方式会给每个序列化对象添加$id属性,避免无限递归,但返回的JSON会包含额外元数据。
3. 使用DTO(数据传输对象)——推荐的长期方案
直接返回EF实体不仅会导致循环引用,还可能暴露敏感/不必要字段,不符合分层架构最佳实践。建议创建DTO封装需要返回给前端的数据:
首先创建对应的DTO类:
// DepartmentDto.cs public class DepartmentDto { public string UserName { get; set; } public string DateResult { get; set; } public string RestDay { get; set; } public string CompanyName { get; set; } // 仅添加前端需要的字段,不要包含导航属性 } // CompanyDto.cs(如果需要返回公司信息) public class CompanyDto { public string CompanyName { get; set; } public string CurrentDepartmentName { get; set; } // 其他需要的字段 }
然后在控制器中将实体映射到DTO:
[HttpPost] public async Task<IActionResult> LoginTablet([FromBody] LoginViewModel model) { try { var department = await _departmentRepository.FindFilterIncludeAsync(x => x.UserName == model.UserName, x => x.Company); if (department == null) return BadRequestResult("Sistemde Böyle Bir Kullanıcı Bulunamadı"); // ... 其他校验逻辑 // 映射到DTO var departmentDto = new DepartmentDto { UserName = department.UserName, DateResult = department.DateResult, RestDay = department.RestDay, CompanyName = department.Company.CompanyName }; var companyDto = new CompanyDto { CompanyName = department.Company.CompanyName, CurrentDepartmentName = department.Name }; return OkResult(new { department = departmentDto, token = _tokenService.GetJWTToken(department.Company) }); } catch (Exception ex) { return BadRequestResult("Beklenmedik Bir Hata Meydana Geldii : " + ex.Message); } }
这种方式完全避免了循环引用,同时让你精确控制返回的数据,是最推荐的解决方案。
4. 限制EF加载的关联数据
如果不想用DTO,也可以在查询时只加载需要的字段,而非整个关联实体:
var department = await _departmentRepository .FindFilterAsync(x => x.UserName == model.UserName) .Select(x => new { x.Id, x.UserName, x.Password, x.LicenceLastTime, x.Active, x.Name, CompanyName = x.Company.CompanyName }) .FirstOrDefaultAsync();
这样查询出的是匿名对象,不包含导航属性,自然不会有循环引用问题,但这种方式灵活性差,维护成本高。
内容的提问来源于stack exchange,提问作者alkincakiralar

