如何处理Entity Framework中的值对象?

Tho*_*der 18 domain-driven-design entity-framework

如何在不污染我的域模型的情况下在实体框架中保留值对象?EF(通常是关系数据库)要求我定义一个键 - 我的值对象没有开箱即用,例如

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

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

   public string Name { get { return this.name; }}
}
Run Code Online (Sandbox Code Playgroud)

另一方面,我不应该解决模型中的持久性问题.我真的应该创建另一个类,其中包含值对象的所有字段加上一个键属性,然后将它们相互映射?我宁愿不.

是否有更优雅的解决方案?

Mar*_*sen 14

Vaughn Vernon在其出色的书" 实施领域驱动设计"中撰写了关于持久值对象(第248页)的文章.

ORM和单值对象

基本思想是将Value的每个属性存储在存储其父Entity的行的不同列中.换句话说,将单个值对象非规范化为其父实体的行.使用列命名约定来清楚地标识和标准化序列化对象的命名方式是有好处的.

ORM和由数据库实体支持的许多值

使用ORM和关系数据库持久化Value实例集合的一种非常简单的方法是将Value类型视为数据模型中的实体.(...)为了实现这一目标,我们可以使用Layer Supertype.

可以在此处找到C#中的有界上下文示例:https://github.com/VaughnVernon/IDDD_Samples_NET


Rya*_*hur 7

我目前正在解决其中一些相同的挑战.我不是真的喜欢向你的基ValueObject<T>类添加一个Id,因为它给所有值对象提供了一个Id,无论它们是否需要加上作为一个值对象,根据定义没有Id,它是继承该基类型的任何东西将不再纯粹意义上的价值对象.

在我走得更远之前,我会注意到编码DDD的一个关键概念是你不必在任何地方都是纯DDD,只要你知道你做出的让步和他们的权衡.话虽这么说,你的方法当然可以被认为是好的,但我相信它会增加一个可能并非真正必要的让步.首先,这会影响值对象的相等性.通过添加Id,两个标签即使具有相同的名称也不再相等.

以下是我解决这种情况的方法:首先是简单的方法,不是真的适用于我认为你的问题,但它很重要.这是Martin答案第一部分中的单值对象.

  • 使值对象成为实体的属性.

只要您的值对象只包含简单的类型属性,Entity Framework就会很好地映射它.

例如:

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

         // Constructors, Factories, Methods, etc
    }
Run Code Online (Sandbox Code Playgroud)

实体框架将处理这一点很好,你将最终得到的是一个表格BlogEntry,它只包含:

  • ID
  • 文本
  • 标签名称

现在我认为这不是你在这种情况下的追求,但是对于许多有价值的物体来说,它的效果非常好.我经常使用的是DateRange值对象,它由几个属性组成.然后在我的域对象上,我只有一个DateRange类型的属性.EF将这些映射到域对象本身的表中.

我提出这个问题是因为回到我们向ValueObject<T>基本类型添加Id所做出的让步,即使它可能没有在您的域对象具体实现中列出,它仍然存在并且仍然会被实体框架拾取这可能是最常见的价值对象用例不再适用.

好的,最后,你的具体情况(我也遇到过几次).这是我如何选择处理实体包含值对象列表的需要.基本上它归结为扩展我们对域的理解.假设Tag值对象用于在博客文章中记录Tag,我看待它的方式是BlogPost包含一个PostTag列表,其值为Tag.是的,它是另一个类,但你不需要为每个值对象添加它,只有当你有一个值对象列表时才需要它,我想更好地表达正在发生的事情.

这里是一个向实体添加值对象列表的示例(使用上面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 };
        }
    }
Run Code Online (Sandbox Code Playgroud)

这将允许您的BlogEntry包含多个标签而不会损害价值对象,实体框架会将其映射得很好而无需做任何特殊的事情.

  • 我最近一直喜欢这种东西的另一种技术是将值对象的集合存储为Json字符串.包含集合的实体对象可以处理进出字符串的解析,以便模型的其余部分仍然可以作为集合使用它,但是对于存储,它作为简单字符串进行处理.如果您需要手动查询,SQL Server也可以使用Json数据,尽管它需要更多工作.如果您的视图使用Ajax/Jquery,您可以直接使用JsonString属性. (3认同)