EF Core 2: прекращение циклической зависимости от отношения "многие ко многим"

Я использую образец базы данных Sakila из MySql на сервере MySql. Схема выглядит следующим образом. EERDiagram Sakila

Важными таблицами являются таблицы store, inventory и film. Между таблицами существует связь многие ко многим, а таблица связывания - это таблица ресурсов.

Я построил эту базу данных в новом проекте dotnetcore, используя EFCore 2. Я пытаюсь получить список магазинов и их список фильмов.

Сущности определяются следующим образом:

Магазин

public class Store
{
    public Store()
    {
        Customer = new HashSet<Customer>();
        Inventory = new HashSet<Inventory>();
        Staff = new HashSet<Staff>();
    }

    public byte StoreId { get; set; }
    public byte ManagerStaffId { get; set; }
    public short AddressId { get; set; }
    public DateTimeOffset LastUpdate { get; set; }

    public Address Address { get; set; }
    public Staff ManagerStaff { get; set; }
    public ICollection<Customer> Customer { get; set; }
    public ICollection<Inventory> Inventory { get; set; }
    public ICollection<Staff> Staff { get; set; }
}

Инвентарь

public partial class Inventory
    {
        public Inventory()
        {
            Rental = new HashSet<Rental>();
        }

        public int InventoryId { get; set; }
        public short FilmId { get; set; }
        public byte StoreId { get; set; }
        public DateTimeOffset LastUpdate { get; set; }

        public Film Film { get; set; }
        public Store Store { get; set; }
        public ICollection<Rental> Rental { get; set; }
    }

Фильм

public partial class Film
    {
 public Film()
 {
     FilmActor = new HashSet<FilmActor>();
     FilmCategory = new HashSet<FilmCategory>();
     Inventory = new HashSet<Inventory>();
 }

 public short FilmId { get; set; }
 public string Title { get; set; }
 public string Description { get; set; }
 public short? ReleaseYear { get; set; }
 public byte LanguageId { get; set; }
 public byte? OriginalLanguageId { get; set; }
 public byte RentalDuration { get; set; }
 public decimal RentalRate { get; set; }
 public short? Length { get; set; }
 public decimal ReplacementCost { get; set; }
 public string Rating { get; set; }
 public string SpecialFeatures { get; set; }
 public DateTimeOffset LastUpdate { get; set; }

 public Language Language { get; set; 
 public Language OriginalLanguage { get; set; }
 public ICollection<FilmActor> FilmActor { get; set; }
 public ICollection<FilmCategory> FilmCategory { get; set; }
 public ICollection<Inventory> Inventory { get; set; }

}

Мой контекст выглядит следующим образом:

  modelBuilder.Entity<Inventory>(entity =>
  {
         entity.ToTable("inventory", "sakila");

         entity.HasIndex(e => e.FilmId)
             .HasName("idx_fk_film_id");

         entity.HasIndex(e => new { e.StoreId, e.FilmId })
             .HasName("idx_store_id_film_id");

И, наконец, репо выглядит следующим образом:

public IEnumerable<Store> GetStores()
{
    return _context.Store.
        Include(a => a.Inventory).
        ToList();
}

Проблема: когда я вызываю этот метод из контроллера для получения списка магазинов, я не получаю ответа json от Postman. Тем не менее, если я отлаживаю список, возвращаемый контроллером, я нахожу список магазинов. Проблема в том, что список содержит: store-> inventory-> film-> store-> inventory-> film-> store ... и т. Д. Создание циклической зависимости, которая заполняет разрешенную память процесса запроса.

Возможные решения: я думаю, это связано с тем, что в контексте оба внешних ключа определены как HasIndex вместо HasKey

entity.HasIndex(e => new { e.StoreId, e.FilmId })
                 .HasName("idx_store_id_film_id");

Когда я определяю его как HasKey, я получаю сообщение об ошибке:

«Отношение от« Rental.Inventory »к« Inventory.Rental »со свойствами внешнего ключа {'InventoryId': int} не может нацеливаться на первичный ключ {'StoreId': byte, 'FilmId': short}, потому что он несовместим. Настройте основной ключ или набор совместимых свойств внешнего ключа для этой связи. '


person axelrotter    schedule 05.11.2018    source источник
comment
Эй, ты нашел какое-нибудь решение для этого?   -  person Hamza    schedule 10.07.2020


Ответы (1)


Чтобы ответить на комментарий @hamzas, я нашел решение этой проблемы. Я использовал EFCore для создания сущностей и DBContext через строительные леса (DB First). Рекомендуется использовать модели (Dtos) для представления данных для клиента. EFCore очень полезен, поскольку дает нам гибкость в доступе к отношениям M к N, как мы того хотим. Это дает нам гибкость для представления этих данных клиенту, как мы хотим.
Каким бы ни был ваш вариант использования. Вы должны преобразовать отношение M к N в модель 1 к N.
Вариант использования №1. Вы хотите показать все фильмы из определенного магазина.
Решение
Шаг №1. Вы создаете StoreDto (модель)

public class StoreDto
{
    int StoreId { get; set; }
    ICollection<FilmDto> Films { get; set; }
       = new List<FilmDto> ();
}

Шаг 2. Создайте FilmDto

public class FilmDto
{
    int FilmId { get; set; }
    int StoreId { get; set; }
    string FilmName { get; set; }
}

Шаг № 3. Вы предоставляете сопоставление с автоматическим сопоставлением.

public class MappingProfiles : Profile 
{
    public MappingProfiles()
    {
        CreateMap<Store, StoreDto>();
        CreateMap<Film, FilmDto>();
    }
}

Шаг № 4. Правильно запрашивайте данные, К сожалению, у меня больше нет этого примера для тестирования этого кода, поэтому здесь вам придется немного поэкспериментировать

public Store GetFilmsForStore(byte StoreId)
{
    
    return _context.Store.
        Include(a => a.Inventory).
        ThenInclude(i => i.Film)
        ToList();
}

В части "Включить" вы хотите получить только записи инвентаризации, где StoreId == Inverntory.StoreId, а затем включить объект Films из результирующего списка. Надеюсь, вы понимаете это. Вы хотите разорвать отношения m и n и заставить их казаться для ваших клиентов похожими на отношения 1 к m.

person axelrotter    schedule 11.07.2020