Entity Framework: цикл ссылок во многих отношениях

У меня есть три сущности 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 17.01.2023 16:37

@Ghassen При попытке получить всех клиентов

Sachihiro 17.01.2023 16:43

Я предполагаю, что вы возвращаете данные с помощью API, можете ли вы поделиться своим запросом?

Ghassen 17.01.2023 16:45

я думаю, это не проблема, dotnet не может сериализовать ваши объекты, потому что есть некоторые циклические зависимости

Ghassen 17.01.2023 16:47

Это не EF Core, это проблема сериализации объектов, которые вы получаете из своего запроса, в JSON. JsonSerializer сериализует Customer, у которого есть Reviews, которые снова указывают на Customer, и так далее.

Valuator 17.01.2023 16:49

@Ghassen Я добавил запрос, который использую для получения всех клиентов.

Sachihiro 17.01.2023 17:04

@Valuator есть идеи, как это исправить?

Sachihiro 17.01.2023 17:04

1/ Не рекомендуется смешивать Ef-запрос и Automapper, вам нужно получить данные, а затем сопоставить их.

Ghassen 17.01.2023 17:12

@Sachihiro используйте атрибут [JsonIgnore] для свойства, которое вы не хотите сериализовать. Вероятно, Customer и Product в вашем Review классе.

Valuator 17.01.2023 17:12

2/ Можете ли вы поделиться содержимым CustomerDto?

Ghassen 17.01.2023 17:13

@Ghassen Я добавил CustomerDto.

Sachihiro 17.01.2023 17:17

@Valuator не будет ли это игнорироваться во всех случаях?

Sachihiro 17.01.2023 17:17

вам нужно просто создать ReviewDto, и он будет работать

Ghassen 17.01.2023 17:26

@Ghassen У меня уже есть ReviewDto

Sachihiro 17.01.2023 17:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
14
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Чтобы решить эту проблему, вам нужно добавить класс 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. Это делает ответ Гассена хорошим.

Спасибо за подробный ответ, я намного лучше понимаю, в чем проблема, и это моя цель этих упражнений.

Sachihiro 17.01.2023 17:48

Другие вопросы по теме