.NET Web API使用ReferenceHandler.IgnoreCycles返回异常JSON求助
问题解答
这不是System.Text.Json的Bug,问题根源是EF Core的实体跟踪/导航属性加载机制与ReferenceHandler.IgnoreCycles序列化逻辑的共同作用:
当你启用ReferenceHandler.IgnoreCycles后,序列化器会跳过循环引用的节点,但EF Core的导航属性(尤其是延迟加载的属性)会在序列化时被触发加载——因为序列化过程会访问Vendor.Products属性。此时EF Core会根据当前上下文的跟踪状态,把之前加载过的实体(甚至是无关的、未完全加载的实体)填充到Products集合中,就出现了你看到的null值或随机产品的情况。
下面是几种保留双向导航属性同时获取正确JSON的可行方案:
1. 使用DTO(数据传输对象)【推荐】
这是最规范的解决方案,完全脱离实体类控制API输出,避免序列化实体带来的耦合和意外问题:
- 定义只包含所需字段的DTO类:
public class ProductDto { public int Id { get; set; } public string ProductName { get; set; } public decimal Price { get; set; } public VendorDto Vendor { get; set; } } public class VendorDto { public int Id { get; set; } public string VendorName { get; set; } // 不包含Products集合,彻底避免循环 } - 在控制器中通过EF投影查询生成DTO:
[HttpGet] public IActionResult GetProducts() { var products = _context.Products .Include(p => p.Vendor) .Select(p => new ProductDto { Id = p.Id, ProductName = p.Name, Price = p.Price, Vendor = new VendorDto { Id = p.Vendor.Id, VendorName = p.Vendor.Name } }) .ToList(); return Ok(products); }
2. 全局或局部忽略Vendor的Products属性序列化
如果不想用DTO,可以直接配置序列化器忽略Vendor.Products属性:
方式一:实体类加特性标记
在Vendor类的Products属性上添加[JsonIgnore]特性:
public class Vendor { public int Id { get; set; } public string Name { get; set; } [JsonIgnore] // 序列化时忽略该属性 public ICollection<Product> Products { get; set; } = new List<Product>(); }
方式二:全局Json配置(不修改实体类)
在Program.cs中配置序列化选项,动态忽略指定属性:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // 注册类型修改器,忽略Vendor的Products属性 options.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { typeInfo => { if (typeInfo.Type == typeof(Vendor)) { var productsProp = typeInfo.Properties.FirstOrDefault(p => p.Name == nameof(Vendor.Products)); if (productsProp != null) { productsProp.IsIgnored = true; } } } } }; });
3. 关闭EF Core的延迟加载
延迟加载是导致序列化时意外加载Products集合的主要原因之一,关闭后可以避免这种情况:
方式一:禁用延迟加载代理
在DbContext配置中关闭:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Your_Connection_String") .UseLazyLoadingProxies(false); }
方式二:移除导航属性的virtual关键字
如果你的导航属性是通过virtual关键字启用延迟加载的,去掉virtual即可:
public class Vendor { public int Id { get; set; } public string Name { get; set; } // 去掉virtual,关闭该属性的延迟加载 public ICollection<Product> Products { get; set; } = new List<Product>(); }
4. 使用无跟踪查询
通过AsNoTracking()让EF Core不跟踪查询到的实体,减少上下文跟踪的其他实体被意外关联的情况:
[HttpGet] public IActionResult GetProducts() { var products = _context.Products .Include(p => p.Vendor) .AsNoTracking() // 不跟踪实体 .ToList(); return Ok(products); }
内容的提问来源于stack exchange,提问作者Mike Smith - MCT
相关产品推荐
相关产品推荐

