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



我有三个实体Customer,ProductReview

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

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

错误消息

System.Text.Json。JsonException:检测到一个可能的对象循环。这可能是由于循环或对象深度大于最大允许深度32。考虑使用ReferenceHandler。保留JsonSerializerOptions以支持循环。

路径:.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; }
}
}

要解决这个问题,你需要像这样添加一个ReviewDto类:

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

}

并更新CustomerDto:

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客户端可能不是您的本意。

可以通过许多不同的方法来防止这种情况。你可以忽略那些会导致循环的属性,但是这个"有点"破坏数据,可能被客户端误解。

另一种方法是将带有循环的类映射到不包含该数据的显式抑制循环的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);
...

说真的,你错过了一个步骤。将返回的实体映射到dto更有意义。dto的目的是根据API客户机的需求塑造响应内容。这使得Ghassen的回答很好。

最新更新