繁体   English   中英

实体框架:多对多关系中的引用循环

[英]Entity Framework : reference loop in many to many relationship

我有这三个实体CustomerProductReview

一个客户可以有很多产品,而一个产品只能有一个客户作为所有者。 一个客户也可以有很多评论,一个评论只能有一个客户。 一个产品可以有很多评论。

似乎我有一个参考循环,下面是我在尝试获取所有客户时得到的JsonException

错误信息

System.Text.Json.JsonException:检测到可能的 object 循环。 这可能是由于循环或 object 深度大于允许的最大深度 32。考虑在 JsonSerializerOptions 上使用 ReferenceHandler.Preserve 来支持循环。

路径:$.rows.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Id。

代码:

namespace Domain.Entities
{
    public partial class Customer
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public virtual ICollection<Review> Reviews { get; set; }
    }

    public partial class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int Price { get; set; }
        public int CustomerId { get; set; }
        public Customer Customer { get; set; }
        public virtual ICollection<Review> Reviews { get; set; }
    }

    public partial class Review
    {
        public int Id { get; set; }
        public int Stars { get; set; }
        public string Description { get; set; }
        public int CustomerId { get; set; }
        public int ProductId { get; set; }
        public Customer Customer { get; set; }
        public Product Product { get; set; }
    }
}

ModelBuilder配置:

// Products configurations 
builder.Ignore(e => e.DomainEvents);
builder.HasKey(t => t.Id);

// Customers configurations
builder.Ignore(e => e.DomainEvents);
builder.HasMany(e => e.Reviews)
       .WithOne(e => e.Customer)
       .HasForeignKey(uc => uc.Id);

builder.HasMany(e => e.MessagesSent)
       .WithOne(e => e.Receiver)
       .HasForeignKey(uc => uc.SenderId)
       .OnDelete(DeleteBehavior.Cascade);

builder.HasMany(e => e.MessagesReceived)
       .WithOne(e => e.Sender)
       .HasForeignKey(uc => uc.ReceiverId)
       .OnDelete(DeleteBehavior.Cascade);

// Reviews configurations
builder.HasKey(t => t.Id);
builder.HasOne(d => d.Customer)
        .WithMany(p => p.Reviews)
        .HasForeignKey(t => t.CustomerId)
        .OnDelete(DeleteBehavior.Cascade);

builder.HasOne(d => d.Product)
        .WithMany(p => p.Reviews)
        .HasForeignKey(t => t.ProductId)
        .OnDelete(DeleteBehavior.Cascade);

关于如何解决此错误的任何想法?

提前致谢,如果您需要更多信息,请告诉我,我会尽快提供。

编辑:这是我用来获取所有客户的查询:

public async Task<PaginatedData<CustomerDto>> Handle(CustomersWithPaginationQuery request)
{
    var filters = PredicateBuilder.FromFilter<Customer>("");
    var data = await _context.Customers
                             .Where(filters)
                             .OrderBy("Id desc")
                             .ProjectTo<CustomerDto>(_mapper.ConfigurationProvider)
                             .PaginatedDataAsync(1, 15);

    return data;
}

编辑#2 :CustomerDto

namespace Application.Customers.DTOs
{
    public partial class CustomerDto : IMapFrom<Customer>
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<Review> Reviews { get; set; }
    }
}

要解决此问题,您需要像这样向 class 添加 ReviewD:

public partial class ReviewDto
    {
        public int Id { get; set; }
        public int Stars { get; set; }
        public string Description { get; set; }
       // ...
   
    }

并将 CustomerD 更新为:

 public partial class CustomerDto : IMapFrom<Customer>
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<ReviewDto> Reviews { get; set; }
    }

正如评论所暗示的那样,问题不在于 EF; 它使用System.Text.Json的默认机制来序列化所有内容,即使存在循环。 问题是你最终达到了给你那个例外的限制。 将这样一个臃肿的负载发送回 API 客户端可能不是你的意图。

您可以通过多种不同的方式来防止这种情况。 您可以 null 找出会导致循环的属性,但这种“某种”会破坏数据并可能被客户误解。

另一种方法是 map 带有 DTO 循环的类,通过不包含该数据或将引用属性(例如 ID 或其他一些引用值)替换为已重复的数据来显式抑制循环。

如果您不想这样做,可以通过使用ReferenceHandler设置为忽略循环来防止异常。

本文档解释了如何做到这一点。 效果等同于手动清空值的第一个解决方案。 该页面的摘录

Employee tyler = new()
{
    Name = "Tyler Stein"
};

Employee adrian = new()
{
    Name = "Adrian King"
};

tyler.DirectReports = new List<Employee> { adrian };
adrian.Manager = tyler;

JsonSerializerOptions options = new()
{
    ReferenceHandler = ReferenceHandler.IgnoreCycles,
    WriteIndented = true
};

string tylerJson = JsonSerializer.Serialize(tyler, options);
...

但是,实际上,您错过了一步。 map 将您返回的实体返回给 DTO 更有意义。 DTO 的目的是根据 API 客户的需求调整响应内容。 这使Ghassen 的回答很好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM