Как мне инкапсулировать коллекцию в объект «сложного типа» с помощью ядра Ef (пример включен)

#c# #entity-framework #ef-core-2.2

#c# #сущность-фреймворк #ef-ядро-2.2

Вопрос:

Я хотел бы отобразить в ядре Ef сложный тип (другими словами, тип, не сопоставленный напрямую с таблицей), который содержит список объектов, которые напрямую сопоставляются с таблицей.

Рассмотрим следующий пример ниже, где вы обнаружите, что логика дневника (из которых я сейчас показываю только addEntry) отделена от класса запроса, поскольку она не имеет никакого отношения к присутствию.

У меня нет проблем с использованием.OwnsOne в ядре Ef при использовании более простых типов, где данные хранятся в одной таблице (например: здесь это будет в таблице запросов); но я понятия не имею или понятия, как бы я сопоставил при настройке этой ситуации.

  • Модель C #:
 public class Request : DbEntity {
   public Diary { get; set; }
}

public class DiaryEntry : DbEntity {
   public DateTimeOffset Timestamp { get; set; }
   public Request Request { get; set; }
   public string Message { get; set; }   
}

public class Diary {
   private readonly List<DiaryEntry> _entries = new List<DiaryEntry>();
   public Request Request { get; set; }
   public IEnumerable<DiaryEntry> Entries => _entries;
   public Diary AddEntry(DiaryEntry diaryEntry) {
      if(diaryEntry != null)
         _entries.Add(diaryEntry);
      return this;
   }
}
 
  • Структура SQL
    • Запросы — это таблица, которая сопоставляется в EFCore с запросом объекта
    • DiaryEntries — это таблица, которая существует в EFCore и косвенно сопоставляется с DbSet с запросом как «hasOne», чтобы он имел отношение.
    • Таблица «Дневник» не существует, это «сложный тип», который инкапсулирует логику, поэтому мне не нужно иметь дело с ней в типе «Запрос» в C#

Комментарии:

1. Поскольку DiaryEntry ссылается на запрос, почему бы не добавить свойство навигации по коллекции в класс запроса, которое позволило бы вам использовать синтаксис EF Core fluent для извлечения списка? Например. public ICollection<DiaryEntry> DiaryEntries { get; set; }

2. Это именно то, чего я хочу избежать, поскольку «Дневник» будет содержать логику, которая полезна только для сбора DiaryEntries и не полезна для класса Request. Другими словами, я хочу максимально уважать SRP и не загромождать запрос логикой, которая не имеет реального значения для самого «запроса».

Ответ №1:

Я не верю, что что-либо подобное поддерживается в Entity Framework, поскольку сложные типы предназначены для поддержки организации свойств одной таблицы в связанные классы без реляционных таблиц. В вашем случае вы хотели бы использовать реляционные таблицы и сопоставить промежуточный класс, чтобы избежать наличия правил управления дочерней коллекцией в родительском классе.

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

Например:

 [Table("Requests")]
public class Request
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int RequestId { get; set; }
    public string Name { get; set; }

    public virtual IEnumerable<DiaryEntry> Diary { get; protected set; } = new List<DiaryEntry>();
}

[Table("DiaryEntries")]
public class DiaryEntry
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int DiaryEntryId { get; set; }
    public string Value { get; set; }

    public virtual Request Request { get; set; }
}
 

Здесь мы раскрываем Дневник, IEnumerable<DiaryEntry> чтобы потребители объекта не могли изменять коллекцию и т. Д. Оттуда функциональность дневника может управляться расширениями:

 public static class DiaryExtensions
{
    public static DiaryEntry AddEntry(this IEnumerable<DiaryEntry> diary, DiaryEntry newEntry)
    {
        ICollection<DiaryEntry> diaryList = (ICollection<DiaryEntry>)diary;
        if (newEntry != null)
            diaryList.Add(newEntry);

        return newEntry;
    }
}
 

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

Комментарии:

1. На самом деле я действительно пошел по аналогичному маршруту сейчас, настолько прагматично, насколько это возможно, и действительно использую функциональность частичных классов, которую мы имеем в языке, чтобы сделать ее по крайней мере «визуально» отдельной. Однако я подумал, что в EF могло быть другое решение, но если нет, то это вполне приемлемо. Спасибо за ваш ответ!