У меня есть три сущности Customer, Product и Review.
У покупателя может быть много продуктов, а у продукта может быть только один покупатель в качестве владельца. У клиента также может быть много отзывов, а у одного отзыва может быть только один клиент. Товар может иметь много отзывов.
Похоже, у меня есть эталонный цикл, и ниже показано JsonException, которое я получаю, пытаясь получить всех клиентов:
Сообщение об ошибке
System.Text.Json.JsonException: обнаружен возможный объектный цикл. Это может быть связано с циклом или с тем, что глубина объекта больше максимально допустимой глубины 32. Рассмотрите возможность использования ReferenceHandler.Preserve в 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; }
}
}
@Ghassen При попытке получить всех клиентов
Я предполагаю, что вы возвращаете данные с помощью API, можете ли вы поделиться своим запросом?
я думаю, это не проблема, dotnet не может сериализовать ваши объекты, потому что есть некоторые циклические зависимости
Это не EF Core, это проблема сериализации объектов, которые вы получаете из своего запроса, в JSON. JsonSerializer сериализует Customer, у которого есть Reviews, которые снова указывают на Customer, и так далее.
@Ghassen Я добавил запрос, который использую для получения всех клиентов.
@Valuator есть идеи, как это исправить?
1/ Не рекомендуется смешивать Ef-запрос и Automapper, вам нужно получить данные, а затем сопоставить их.
@Sachihiro используйте атрибут [JsonIgnore] для свойства, которое вы не хотите сериализовать. Вероятно, Customer и Product в вашем Review классе.
2/ Можете ли вы поделиться содержимым CustomerDto?
@Ghassen Я добавил CustomerDto.
@Valuator не будет ли это игнорироваться во всех случаях?
вам нужно просто создать ReviewDto, и он будет работать
@Ghassen У меня уже есть ReviewDto





Чтобы решить эту проблему, вам нужно добавить класс 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, которые явно подавляют цикл, не включая эти данные или заменяя ссылочное свойство (например, идентификатор или какое-либо другое ссылочное значение) на данные, которые были повторены.
Если вы не хотите этого делать, вы можете предотвратить исключение, используя 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. Это делает ответ Гассена хорошим.
Спасибо за подробный ответ, я намного лучше понимаю, в чем проблема, и это моя цель этих упражнений.
Когда у вас появилась эта ошибка?