【问题标题】:JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger thanJsonException:检测到不支持的可能对象循环。这可能是由于循环或对象深度大于
【发布时间】:2020-05-28 12:54:45
【问题描述】:

在我的 web api 中,当我运行从数据库获取数据的项目时出现此错误 .net 核心 3.1

JsonException:检测到不支持的可能对象循环。这可能是由于循环或对象深度大于最大允许深度 32 造成的。

这些是我的代码 我的模特

 public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string ProductText { get; set; }
    public int ProductCategoryId { get; set; }
    [JsonIgnore]
    public virtual ProductCategory ProductCategory { get; set; }
}

我的 productCategory 类是:

 public class ProductCategory
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string CatText { get; set; }
    public string ImagePath { get; set; }
    public int Priority { get; set; }
    public int Viewd { get; set; }
    public string Description { get; set; }
    public bool Active { get; set; }
    public DateTime CreateDate { get; set; }
    public DateTime ModifyDate { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

我的仓库是

public async Task<IList<Product>> GetAllProductAsync()
    {
        return await  _context.Products.Include(p => p.ProductCategory).ToListAsync(); 
    }

我的界面

public interface IProductRepository
{
   ...
    Task<IList<Product>> GetAllProductAsync();
 ...
}

这是我在 api 项目中的控制器

 [Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    private readonly IProductRepository _productRepository;

    public ProductsController(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
    [HttpGet]
    public ActionResult Get()
    {
        return Ok(_productRepository.GetAllProduct());
    }
}

当我运行 api 项目并输入此网址时:https://localhost:44397/api/products 我得到了那个错误, 解决不了

【问题讨论】:

  • 你的产品和产品类别是如何联系在一起的?
  • 可能需要在从 ProductCategory 到 Product 的 FK 属性上使用 [JsonIgnore]
  • 我更新了我的问题,但错误存在。
  • ReferenceLoopHandling.Ignore 可能是一个选项
  • 您的ProductCategory 中可能引用了Product 类。然后你创建了一个参考循环。

标签: c# api asp.net-core asp.net-core-mvc


【解决方案1】:

确保您在正确的字段中有 [JsonIgnore] 以避免循环引用。

在这种情况下,您将需要

 public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string ProductText { get; set; }
    [JsonIgnore]
    public virtual ProductCategory ProductCategory { get; set; }
}

您可能不需要 ProductCategoryId 字段(取决于您是否先使用 EF 和代码来定义数据库)

编辑 - 对 noruk 的回答

连接的对象和导航属性经常混淆。您可以在 JSON 中获取所需的数据,还可以定义 EF 结构以获得正确的 DB 结构(外键、索引等)。

以这个简单的例子为例。产品(例如 T 恤)有多种尺寸或 SKU(例如小号、大号等)

  public class Product
    {
     [Key]
     [MaxLength(50)]
     public string Style { get; set; }
     [MaxLength(255)]
     public string Description { get; set; }
     public List<Sku> Skus { get; set; }
    }
    
    public class Sku
    {
      [Key]
      [MaxLength(50)]
      public string Sku { get; set; }
      [MaxLength(50)]
      public string Barcode { get; set; }
      public string Size { get; set; }
      public decimal Price { get; set; }
      // One to Many for Product
      [JsonIgnore]
      public Product Product { get; set; }
    }

您可以在此处对产品进行序列化,并且 JSON 数据将包括 SKU。这是正常的做事方式。

但是,如果您对 SKU 进行序列化,您将不会获得它的父产品。包含导航属性会使您进入可怕的循环并抛出“检测到对象循环”错误。

我知道这在某些用例中会受到限制,但我建议您遵循这种模式,如果您希望父对象可用,您可以根据子对象单独获取它。

var parent = dbContext.SKUs.Include(p => p.Product).First(s => s.Sku == "MY SKU").Product

【讨论】:

  • 我这样做并删除 ProductId 但错误仍然存​​在也尝试在 services.AddControllers() 中设置选项但不起作用
  • @CueBall 我使用.Net 5,在我的情况下,因为我想包含导航属性。如果我使用[JsonIgnore],它将不会被添加到 JSON 结果中。所以,如果我不使用[JsonIgnore],我会得到:检测到可能的对象循环那么,如何包含导航属性?
  • @noruk - 请查看编辑后的答案。我目前使用的是 .NET Core 3.1。
【解决方案2】:

发生这种情况是因为您的数据有一个引用循环。

例如

// this example creates a reference loop
var p = new Product()
     { 
        ProductCategory = new ProductCategory() 
           { products = new List<Product>() }
     };
    p.ProductCategory.products.Add(p); // <- this create the loop
    var x = JsonSerializer.Serialize(p); // A possible object cycle was detected ...

你不能在新的System.Text.Json(netcore 3.1.1)中处理引用循环的情况,除非你完全忽略一个引用并且它总是不是一个好主意。 (使用[JsonIgnore]属性)

但您有两种选择来解决此问题。

  1. 您可以在项目中使用Newtonsoft.Json 而不是System.Text.Json(我为您链接了一篇文章)

  2. 从 dotnet5 库(通过 Visual Studio 的 NuGet 客户端)下载 System.Text.Json 预览包版本 5.0.0-alpha.1.20071.1

选项 1 用法:

services.AddMvc()
     .AddNewtonsoftJson(
          options => {
           options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; 
      });
// if you not using .AddMvc use these methods instead 
//services.AddControllers().AddNewtonsoftJson(...);
//services.AddControllersWithViews().AddNewtonsoftJson(...);
//services.AddRazorPages().AddNewtonsoftJson(...);

选项 2 用法:

// for manual serializer
var options = new JsonSerializerOptions
{
    ReferenceHandling = ReferenceHandling.Preserve
};

string json = JsonSerializer.Serialize(objectWithLoops, options);

// -----------------------------------------
// for asp.net core 3.1 (globaly)
 services.AddMvc()
  .AddJsonOptions(o => {
     o.JsonSerializerOptions
       .ReferenceHandling = ReferenceHandling.Preserve  
            });

这些序列化程序具有ReferenceLoopHandling 功能。

  • 编辑 ReferenceHandling 在 DotNet 5 中更改为 ReferenceHandler

但如果您决定只忽略一个参考,请在其中一个属性上使用[JsonIgnore]。但即使您没有引用循环,它也会导致您对该字段的 API 响应为空。

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string ProductText { get; set; }
    
    public int ProductCategoryId { get; set; }
    // [JsonIgnore] HERE or
    public virtual ProductCategory ProductCategory { get; set; }
}

public class ProductCategory
{
    public int Id { get; set; }
    // [JsonIgnore] or HERE
    public ICollection<Product> products {get;set;}
}

【讨论】:

  • System.Text.Json v5 preview6 开始,ReferenceHandling 现在是ReferenceHandler。见:github.com/dotnet/runtime/pull/37296/files
  • @AliReza 我正在使用 .net 核心,我想急切地加载表格,例如 public List&lt;Category&gt; Category { get; set; } 所以我使用 context.Customer.Include(c=&gt;c.Customer); 但我面临可能的对象周期。如果我使用 [JsonIgnore] 我将如何从类别中检索数据以显示它们?
  • @noruk 我不建议使用JsonIgnore 你现在有 3 个选项。首先,您可以使用 Newtonsoft.Json 或 Dotnet 5(因为您可以使用这些框架处理循环)第二个选项是反向查询(尝试从类别context.Category.Include(c=&gt;c.Customer).Select(q=&gt; q.Custormer) 中选择您的客户)如果您不这样做,最后一个是忽略其中一个关系不需要使用JsonIgnore
  • 感谢您的回复。我使用.Net 5 web API,在它的启动中我从控制器放置services.AddControllers().AddNewtonsoftJson(options =&gt; options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);是返回:return Ok(JsonSerializer.Serialize(getEmployeeById));如果我在导航属性中使用[JsonIgnore]它可以工作,但是因为我想包含导航属性,然后我不;t 使用 [JsonIgnore] 但我得到 可能的对象循环 。是不是我错过了什么?
  • @noruk 你应该打开一个单独的问题,但看起来你正在使用 .net 5 和 Newtonsoft 框架。如果您使用的是 .net 5,则不需要 newtonsoft 软件包。
【解决方案3】:

最后修复了我的 System.Text.Json 而不是 NewtonSoft.Json 使用

var options = new JsonSerializerOptions()
        {
            MaxDepth = 0,
            IgnoreNullValues = true,
            IgnoreReadOnlyProperties = true
        };

使用选项序列化

objstr = JsonSerializer.Serialize(obj,options);

【讨论】:

  • 所以这里的问题是最大深度为 0 时,您将无法获得数据之间的任何关系(我认为)。 IE。包含语句将不再起作用。
【解决方案4】:

对于 net core 3.1,您必须在 Startup.cs 中添加:

services.AddMvc.AddJsonOptions(o => {
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.JsonSerializerOptions.MaxDepth = 0;
})

并使用 nuget.org 至少导入这个包,包括预发布:

<PackageReference Include="System.Text.Json" Version="5.0.0-rc.1.20451.14" />

【讨论】:

  • 我 100% 明白为什么这是避免递归序列化的好方法,并且我绝对容忍使用像 NewtonSoft 这样好的 JSON 序列化程序。但是,我会反对这种方法,因为在许多情况下,您会希望通过 API、接口或类似的方式对具有多个层次结构级别的数据进行序列化。我建议了解您的数据是这里的关键,并知道要获取什么以及何时获取。理想情况下,从序列化中删除 DB 导航属性,以便以另一种方式获取数据。您通常会得到一个更通用的解决方案。
【解决方案5】:

我有同样的问题,我的解决方法是添加 async 和 await 关键字,因为我在我的业务逻辑上调用 async 方法。

这是我的原始代码:

[HttpGet]
public IActionResult Get()
{
   //This is async method and I am not using await and async feature .NET which triggers the error
   var results = _repository.GetAllDataAsync(); 
   return Ok(results);
}

到这个:

HttpGet]
public async Task<IActionResult> Get()
{
   var results = await _repository.GetAllDataAsync();
   return Ok(results);
}

【讨论】:

    【解决方案6】:

    以下代码在 dotnet 5.0 中为我工作:

      services.AddControllersWithViews()
                            .AddJsonOptions(o => o.JsonSerializerOptions
                            .ReferenceHandler = ReferenceHandler.Preserve);
    

    【讨论】:

    • 对我来说不是.....
    【解决方案7】:

    .NET 5 Web API

        public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddControllers()
                .AddJsonOptions(o => o.JsonSerializerOptions
                    .ReferenceHandler = ReferenceHandler.Preserve);
        }
    

    【讨论】:

      【解决方案8】:

      我的项目构建时出现类似错误。

      这是之前的代码

      public class PrimaryClass {
        public int PrimaryClassId
        public ICollection<DependentClass> DependentClasses { get; set; }
      }
      
      public class DependentClass {
        public int DependentClassId { get; set; }
        public int PrimaryClassId { get; set; }
        public PrimaryClass primaryClass { get; set; }
      }
      

      我从 DependentClass 模型中拿走了 PrimaryClass 对象。

      代码后

      public class PrimaryClass {
        public int PrimaryClassId
        public ICollection<DependentClass> DependentClasses { get; set; }
      }
      
      public class DependentClass {
        public int DependentClassId { get; set; }
        public int PrimaryClassId { get; set; }
      }
      

      我还必须从

      调整OnModelCreating方法
      modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses).WithOne(d => d.primaryClass).HasForeignKey(d => d.PrimaryClassId);
      

      modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses);
      

      正在运行的 DbSet 查询是

      public async Task<List<DependentClass>> GetPrimaryClassDependentClasses(PrimaryClass p)
      {
        return await _dbContext.DependentClass.Where(dep => dep.PrimaryClassId == p.PrimaryClassId).ToListAsync();
      }
      

      错误可能与这 3 段代码中的任何一段有关,但是从依赖类中删除主对象引用并调整 OnModelCreating 解决了错误,我只是不确定为什么会导致循环。

      【讨论】:

        【解决方案9】:

        就我而言,问题在于创建实体关系时。我像这样在依赖实体中使用外键链接主实体

        [ForeignKey("category_id")]
        public Device_Category Device_Category { get; set; }
        

        我还提到了主实体中的 dipendend 实体。

         public List<Device> devices { get; set; }
        

        这创造了一个循环。

        依赖实体

          public class Device
            {
                [Key]
                public int id { get; set; }
                public int asset_number { get; set; }
                public string brand { get; set; }
                public string model_name { get; set; }
                public string model_no { get; set; }
                public string serial_no { get; set; }
                public string os { get; set; }
                public string os_version { get; set; }
                public string note { get; set; }
                public bool shared { get; set; }
                public int week_limit { get; set; }
                public bool auto_acceptance { get; set; }
                public bool booking_availability { get; set; }
                public bool hide_device { get; set; }
                public bool last_booked_id { get; set; }
        
        
                //getting the relationships category 1 to many 
                public int category_id { get; set; }
        
                [ForeignKey("category_id")]
                public Device_Category Device_Category { get; set; }
        
                public List<Booking> Bookings { get; set; } 
               
            }
        

        主要实体

            public class Device_Category
            {
                public int id { get; set; }
                public string name { get; set; }
        
                public List<Device> devices { get; set; }
            }
        }
        

        所以我评论了

        public List<Device> devices { get; set; }
        

        在主要实体 (Device_Category) 内部并解决了问题

        【讨论】:

          【解决方案10】:

          在 .Net 6 中,您可以使用 System.Text.Json 在 Program.cs 中像这样使用 AddControllersWithViews 初始化启动操作,

          using System.Text.Json.Serialization;
          
          builder.Services.AddControllersWithViews()
                          .AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
          

          你也可以像这样使用AddMvc

          builder.Services.AddMvc()
                          .AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
          

          但引用Ryan

          asp.net core 3.0+ 模板使用这些新的 方法AddControllersWithViews,AddRazorPages,AddControllers 而不是 AddMvc。

          我会推荐使用第一个解决方案。

          【讨论】:

            猜你喜欢
            • 2020-07-26
            • 1970-01-01
            • 2021-03-17
            • 2020-04-27
            • 2020-07-06
            • 2021-11-19
            • 2020-03-30
            • 2021-08-30
            • 2021-08-01
            相关资源
            最近更新 更多