web-dev-qa-db-ja.com

Entity Frameworkで値オブジェクトを処理する方法は?

ドメインモデルを汚染せずにEntityFrameworkで値オブジェクトを永続化するにはどうすればよいですか? 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; }}
}

一方で、モデルの永続性の懸念に対処するべきではありません。値オブジェクトのすべてのフィールドとキープロパティを含む別のクラスを作成して、それらを相互にマップする必要がありますか?私はむしろないと思います。

もっとエレガントな解決策はありますか?

19

Vaughn Vernonは、彼の優れた本 Implementing Domain-Driven Design で、永続的な値オブジェクト(248ページ)について書いています。

ORMおよび単一値オブジェクト

基本的な考え方は、値の各属性を、その親エンティティが格納されている行の個別の列に格納することです。別の言い方をすれば、単一の値オブジェクトはその親エンティティの行に非正規化されます。シリアル化されたオブジェクトの命名方法を明確に識別して標準化するために、列の命名規則を採用することには利点があります。

ORMとデータベースエンティティに裏打ちされた多くの値

ORMとリレーショナルデータベースを使用してValueインスタンスのコレクションを永続化するための非常に簡単なアプローチは、Valueタイプをデータモデルのエンティティとして扱うことです。 (...)これを実現するために、 Layer Supertype を使用できます。

C#の制限付きコンテキストのサンプルはここにあります: https://github.com/VaughnVernon/IDDD_Samples_NET

13
Martin4ndersen

私は現在、これらの同じ課題のいくつかに取り組んでいます。基本のValueObject<T>クラスにIDを追加するのはあまり好きではありません。これは、必要かどうかに関係なくすべての値オブジェクトにIDを与えるだけでなく、定義上、値オブジェクトにはIDがないため、それを継承するものです。基本型は、純粋な意味での値オブジェクトではなくなります。

先に進む前に、DDDをコーディングする際の重要な概念は、どのような譲歩とそのトレードオフを知っている限り、どこでも純粋なDDDである必要はないということです。そうは言っても、あなたのアプローチは確かにうまくいくと考えることができますが、それは本当に必要ではないかもしれない譲歩を追加すると私は信じています。主に、これは値オブジェクトの同等性に影響を与えます。 Idを追加すると、同じ名前であっても2つのタグが等しくなくなります。

この状況に対する私のアプローチは次のとおりです。まず、簡単なアプローチです。問題が何であるかにはあまり当てはまりませんが、重要です。これは、マーティンの回答の最初の部分にある単一値オブジェクトです。

  • 値オブジェクトをエンティティのプロパティにします。

値オブジェクトが単純な型プロパティのみで構成されている限り、EntityFrameworkはこれを適切にマップします。

例えば:

    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
  • テキスト
  • タグ名

この場合、それは実際にはあなたが望んでいることではないと思いますが、多くの値オブジェクトにとってはうまく機能します。私が頻繁に使用するのは、いくつかのプロパティで構成されるDateRange値オブジェクトです。次に、ドメインオブジェクトに、タイプDateRangeのプロパティがあります。 EFは、それらをドメインオブジェクト自体のテーブルにマップします。

IdをValueObject<T>基本型に追加することで行った譲歩に戻ると、これを取り上げます。これは、IDがドメインオブジェクトの具体的な実装にリストされていない場合でも、まだ存在しており、引き続き取得されます。 Entity Frameworkによるもので、おそらく最も一般的な値オブジェクトのユースケースは、もは​​やそれほどうまく機能していません。

OK、最後に、あなたの特定のケースに移ります(私も数回遭遇しました)。これが、エンティティが値オブジェクトのリストを含む必要性を処理することを選択した方法です。基本的には、ドメインの理解を深めることになります。タグ値オブジェクトがブログ投稿にタグを記録するためのものであると仮定すると、私が見ると、BlogPostにはタグの値を持つPostTagのリストが含まれています。はい、もう1つのクラスですが、すべての値オブジェクトに追加する必要はありません。値オブジェクトのリストがある場合にのみ必要であり、何が起こっているかをより適切に表現できると思います。

したがって、値オブジェクトのリストをエンティティに追加する例を次に示します(上記のタグの値オブジェクトを使用)。

    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に複数のタグを含めることができ、EntityFrameworkは特別なことをすることなくタグを適切にマッピングします。

6
Ryan McArthur