[英]Entity Framework : reference loop in many to many relationship
我有这三个实体Customer
、 Product
和Review
。
一个客户可以有很多产品,而一个产品只能有一个客户作为所有者。 一个客户也可以有很多评论,一个评论只能有一个客户。 一个产品可以有很多评论。
似乎我有一个参考循环,下面是我在尝试获取所有客户时得到的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.