Как работать с объектами ценности в Entity Framework?

Как сохранить объекты объектов в Entity Framework без загрязнения моей модели домена? EF (ну, реляционные DB вообще) требуют, чтобы я определил ключ - который мои объекты значения не имеют из коробки, например

public class Tag : ValueObject<Tag>
{
   private readonly string name;

   public Tag(string name)
   {
      this.name = name;
   }

   public string Name { get { return this.name; }}
}

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

Возможно ли более элегантное решение?

Ответы

Ответ 1

Вон Вернон пишет о сохраняющихся объектах ценности (стр. 248) в своей замечательной книге Внедрение управляемого доменом дизайна.

ORM и объекты с одним значением

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

ORM и многие значения, поддерживаемые сущностью базы данных

Очень простой подход к сохранению коллекции экземпляров Value с использованием ORM и реляционной базы данных заключается в том, чтобы рассматривать тип Value как объект в модели данных. (...) Для этого мы можем использовать Суперверс слоя.

Примеры ограниченных контекстов в С# можно найти здесь: https://github.com/VaughnVernon/IDDD_Samples_NET

Ответ 2

В настоящее время я работаю над некоторыми из этих же задач. Я не являюсь поклонником добавления Id в ваш базовый класс ValueObject<T>, так как это дает идентификатор всем объектам значений, независимо от того, нужны они или нет, а также как объект значения по определению не имеет идентификатора, это что-то, наследующее этот базовый тип больше не будет объектом значения в чистом смысле.

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

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

  • Сделать объект Value свойством объекта.

Пока ваш объект значения состоит только из простых свойств типа, Entity Framework отобразит это просто отлично.

Пример:

    public class BlogEntry : Entity<Guid>
    {
         public String Text { get; private set; }
         public Tag Tag { get; private set; }

         // Constructors, Factories, Methods, etc
    }

Entity Framework справится с этим просто отлично, в итоге вы получите единственную таблицу BlogEntry, состоящую просто из:

  • Id
  • Текст
  • TAG_NAME

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

Я рассказываю об этом, потому что возвращаясь к концессии, которую мы сделали, добавляя Id к базовому типу ValueObject<T>, даже несмотря на то, что этот идентификатор не может быть указан в конкретной конкретной реализации объекта домена, он все еще существует и будет по-прежнему собираться что для этого, возможно, наиболее распространенный случай использования объекта ценности больше не работает так же хорошо.

ОК, наконец, на ваш конкретный случай (который я тоже запустил несколько раз). Вот как я решил обработать необходимость в том, чтобы сущность содержала список объектов Value. В основном это сводится к расширению нашего понимания домена. Предполагая, что объект значения тега предназначен для записи тега в сообщении в блоге, способ, которым я смотрю на него, заключается в том, что BlogPost содержит список PostTag со значением Tag. Да, это еще один класс, но вам не нужно добавлять его для каждого объекта значения, он нужен только тогда, когда у вас есть список объектов значений, и я думаю, что лучше выражает то, что происходит.

Итак, вот пример добавления списка объекта значения к сущности (с использованием вашего объекта значения тега выше):

    public class BlogEntry : Entity<Guid>
    {
         public String Text { get; private set; }
         public ICollection<PostTag> PostTags { get; private set; }

         // Constructors:
         private BlogEntry(Guid id) : base(id) { }
         protected BlogEntry() : this(Guid.NewGuid()) { }

         // Factories:
         public static BlogEntry Create (String text, ICollection<PostTag> tags = null)
         {
             if(tags == null) { tags = new List<PostTag>(); }
             return new BlogEntry(){ Text = text, Tags = tags };
         }        

         // Methods:
         public void AddTag(String name)
         {
             PostTags.Add(PostTag.Create(name));
         }
    }

    public class PostTag : Entity<Guid>
    {
        // Properties:
        public Tag Tag { get; private set; }
        public DateTime DateAdded { get; private set; } // Properties that aren't relevant to the value of Tag.

        // Constructors:
        private PostTag(Guid id) : base(id) { }
        protected PostTag() : this(Guid.NewGuid()) { }

        // Factories:
        public static PostTag Create(Tag tag) 
        { 
            return new PostTag(){ Tag = tag, DateAdded = DateTime.Now };
        }

        public static PostTag Create(Tag tag, DateTime dateAdded) 
        { 
            return new PostTag(){ Tag = tag, DateAdded = dateAdded };
        }
    }

Это позволит вашей BlogEntry содержать несколько тегов без компрометации объектов значений, а Entity Framework будет отображать ее просто отлично, без необходимости делать что-либо особенное.