Есть ли улучшения в дизайне сущностей в EF Core?

Я пытаюсь создать систему заказов. Базовым объектом является Sku, который представляет некоторый артикул:

public class Sku
{
    [Key]
    public Guid Id { get; set; }
    //a lot of Sku information here
}

Также у меня есть сущности Customer и OrderPicker

public class Customer
{
    [Key]
    public Guid Id { get; set; }
    // some Customer info here
    public List<Order>? Orders { get; set; }
}

public class OrderPicker
{
    [Key]
    public Guid Id { get; set; }
    //some picker data here
    public List<Order>? Orders { get; set; }
}

Затем я хочу создать объект, который представляет сам заказ. Очевидно, что у заказа есть только один клиент, а у клиента много заказов. Насколько я понимаю, эту связь можно смоделировать с помощью следующего кода:

public class Order
{
      [Key]
      public Guid Id { get; set; }
      [Required]
      public Customer Customer { get; set; }
      public Guid CustomerGuid { get; set; }
      //order data
}

И в AppContext:

modelBuilder.Entity<Order>()
                .HasOne(o => o.Customer)
                .WithMany(c => c.Orders)
                .HasForeignKey(o => o.CustomerGuid)
                .HasPrincipalKey(c => c.Id);

То же самое относится и к отношению «заказ-сборщик».

Проблемы возникают, когда я пытаюсь смоделировать отношения между заказом и товаром. Предположим, что заказ должен содержать много Sku в паре с их количеством в этом заказе. Мне пришла в голову идея ввести сущность коннектора OrderItem, которая содержит следующие поля:

[Owned]
public class OrderItem
{
    public Guid SkuId { get; set; }
    public Sku Sku { get; set; }
    public Order Order { get; set; }
    public uint Quantity { get; set; }
}

Тогда в Order я могу объявить ICollection<OrderItems> и работать с ними:

public class Order
{
      [Key]
      public Guid Id { get; set; }
      [Required]
      public Customer Customer { get; set; }
      public Guid CustomerGuid { get; set; }
      public ICollection<OrderItem> OrderItems { get; set; }
      //order data
}

На этом этапе я не уверен, как я могу указать FK и PK для этого объекта и действительно ли они ему нужны.

Тогда мне нужно держать запас для SKUS. Итак, я создал объект StockItem:

public class StockItem
{
    public Sku Sku { get; private set; }
    [Key]
    public Guid SkuId { get; private set; }
    [Required]
    public uint StockBalance { get; private set; }
}

Вышеупомянутое не работает, при попытке добавить первоначальную миграцию выдаются разные ошибки. Все ошибки связаны с StockItem и его связью с Sku. Например:

Свойство "Sku" "StockItem.Sku" не может быть сопоставлено, поскольку поставщик базы данных не поддерживает этот тип. Рассмотрите возможность преобразования значения свойства в тип, поддерживаемый базой данных, с помощью преобразователя значений.

Итак, у меня есть 2 вопроса:

  • Как можно решить проблемы с OrderItem и StockItem?
  • Достаточно ли хорош мой дизайн сущности и можно ли его улучшить?
Стоит ли изучать 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
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Одной из проблем может быть использование SkuId для PK StockItem. Похоже, что Sku по сути похож на продукт? OrderItem может иметь собственный PK, например OrderItemId, ссылающийся на Sku. Если вы хотите отслеживать уровни запасов (баланс) для Skus, вы можете создать отдельную таблицу/сущность для StockItem/StockLevel, которая может использовать SkuId в качестве PK и FK для Sku.

Набросок для размышления:

public class Order
{
      [Key]
      public Guid Id { get; set; }
      [Required]
      [ForeignKey("CustomerId")]
      public virtual Customer Customer { get; set; }
      //order data

     public ICollection<OrderItem> OrderItems { get; private set; } = new List<OrderItem>();
}

public class OrderItem
{
    [Key]
    public Guid Id { get; set; }
    // order item details. such as Quantity.

    [ForeignKey("OrderId")]
    public virtual Order Order { get; set; }
    [ForeignKey("SkuId")]
    public virtual Sku Sku { get; set; }
}

public class Sku
{
    [Key]
    public Guid Id { get; set; }
    // SKU (product?) fields.
}

public class StockLevel
{
   [Key, ForeignKey(nameof(Sku))]
   public Guid Id { get; set; }
   public uint StockBalance { get; set; }

   public virtual Sku Sku { get; set; }
}

При отображении отношений у меня есть одно предложение: не объявлять свойства FK, а использовать теневые свойства. Это устраняет проблему необходимости поддерживать два источника достоверности, которые могут время от времени различаться и приводить к проблемам поведения в зависимости от того, какой из них вы используете, когда свойство навигации загружено или нет. Отношение между заказом и элементом заказа - один ко многим. В одном заказе много позиций заказа. Свойство обратной навигации (OrderItem.Order) является необязательным, и если оно не нужно, я бы просто удалил его и ссылался на элементы заказа исключительно через совокупный корень заказа. Отношения между OrderItem и Sku являются многими-к-одному. Многие позиции заказа могут относиться к одному и тому же артикулу. Order Item будет иметь Sku ID FK, Sku, как правило, не должен иметь коллекцию OrderItems. Связь для Sku и StockLevel будет один к одному, где SKU является основным в связи, где один StockLevel может ссылаться на него, используя один и тот же PK. Обратите внимание, что в StockLevel столбец Id действует как PK и FK для Sku.

Другое предложение, когда речь идет о коллекциях в сущностях, — всегда инициализировать их. Либо в строке, как в примере выше, либо в конструкторе. С сущностями вы хотите избежать повторной инициализации каким-либо внешним кодом набора связанных сущностей, поэтому Setter делается закрытым. Если бы какой-либо код установил order.OrderItems = new List<OrderItem>();, это довольно легко привело бы к проблемам.

Мы надеемся, что это должно дать вам некоторые идеи по настройке отношений, но не стесняйтесь расширять/комментировать любые конкретные вопросы или сценарии, с которыми у вас все еще есть проблемы.

Я много раз видел virtual ключевое слово в документах, но не объясняется, почему мы должны отмечать свойства virtual. Так будем? Если должны, то почему?

Slepoyi 21.04.2023 11:10

Также я настроил OrderItem как собственный тип, потому что мне не нужно иметь DbSet<OrderItem> в моем AppContext. Вы удалили эту аннотацию. Могу ли я оставить его на месте? И если можно, то лучше оставить или удалить?

Slepoyi 21.04.2023 11:23

Вы упомянули, что лучше избегать присваивания внешних значений связанным сущностям. Но что, если система предназначена для такого поведения? Это действительно проблема дизайна или просто момент, о котором нужно знать? Например, у заказа есть один клиент, который уже известен при инициализации заказа, но мы не знаем, кто будет собирать этот заказ, потому что это зависит от того, какой сборщик освободится первым.

Slepoyi 21.04.2023 19:08

Только когда дело доходит до повторной инициализации коллекций. Если у вас есть что-то подобное, и у него есть ссылка на средство выбора, это обычно будет ссылка с нулевым значением, которая будет установлена ​​​​позже и, возможно, изменится с одного средства выбора/пользователя на другого. Это прекрасно. Только с коллекциями может показаться, что быстрее/проще изменить коллекцию, установив ее в новый список, содержащий новый набор элементов, а не удаляя и добавляя изменения по отдельности. Это то, чего следует избегать, поэтому хорошо инициализировать коллекции один раз и сделать установщик закрытым.

Steve Py 22.04.2023 11:43

В некоторых гайдах я видел примеры, когда в связях один-ко-многим авторы указывают ICollection<EntityB> в EntityA и указывают только EntityAId в EntityB без указания свойства навигации public virtual EntityA и без каких-либо аннотаций. Можно ли применить такой подход к моему случаю? Если да, то как EF понимает, что EntityAId является ключом отношения?

Slepoyi 22.04.2023 11:53

Это можно сделать даже без EntityAId, объявленного в EntityB через теневые свойства. EF может управлять FK по соглашению, если имя FK соответствует связанному типу. Отношение будет отображаться явно в OnModelCreating или с использованием конфигурации типа сущности, поэтому, например, modelBuilder.Entity<EntityA>().HasMany(x => x.EntityBs).WithOne(); должно работать, если таблица EntityB имеет EntityAId в качестве обратной стороны FK. Если у вас было другое имя FK, например «ParentId», дополните его: .WithOne().ForeignKey("ParentId");

Steve Py 22.04.2023 12:02

Вау, это суперинформативно, спасибо!

Slepoyi 22.04.2023 15:11

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