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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:57:27