【问题标题】:How to deal with value objects in Entity Framework?如何处理实体框架中的值对象?
【发布时间】:2015-05-23 03:06:59
【问题描述】:

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

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

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

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

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

有没有更优雅的解决方案?

【问题讨论】:

  • 您可以继承该类并将关键属性添加到派生类
  • 我想到了这一点,但最终每个值对象还是有 2 个类。

标签: entity-framework domain-driven-design


【解决方案1】:

Vaughn Vernon 在他的优秀著作Implementing Domain-Driven Design 中写到了持久值对象(第 248 页)。

ORM 和单值对象

基本思想是将 Value 的每个属性存储在存储其父实体的行的单独列中。换句话说,单个值对象被非规范化到其父实体的行中。使用列命名约定来明确标识和标准化序列化对象的命名方式是有好处的。

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

使用 ORM 和关系数据库持久化 Value 实例集合的一种非常直接的方法是将 Value 类型视为数据模型中的实体。 (...) 为此,我们可以使用Layer Supertype

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

【讨论】:

  • 是的,这就是我最终所做的。 ValueObject 现在有一个整数键,所有派生的值对象也是如此。这将相当于 n+1 类而不是 2n 类。 ValueObject 类是共享内核的一部分,因此它与模型没有直接关系。
  • 除非处理 1-N(或多对多),否则使用实体框架复杂类型。
【解决方案2】:

我目前正在应对其中一些相同的挑战。我真的不喜欢将 Id 添加到您的基础 ValueObject&lt;T&gt; 类,因为这为所有值对象提供了一个 Id,无论它们是否需要加上作为定义的值对象没有 Id,它是任何继承该基类型的东西将不再是纯粹意义上的值对象。

在进一步讨论之前,我要指出的是,编码 DDD 的一个关键概念是,您不必在任何地方都是纯 DDD,只要您知道自己做出了哪些让步以及它们的取舍。话虽如此,您的方法当然可以被认为是好的,但我相信它增加了一个可能并不真正必要的让步。首先,这会影响您的值对象的相等性。加上Id,两个Tag,即使同名也不再相等。

以下是我对这种情况的处理方法: 首先是简单的,并不真正适用于我认为你的问题,但它很重要。这是马丁答案第一部分中的单值对象。

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

只要你的值对象只包含简单的类型属性,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,其中包含以下内容:

  • 身份证
  • 文字
  • 标签名称

现在我认为,在这种情况下,这并不是您真正想要的,但对于许多价值对象来说,它的效果很好。我经常使用的一个是由多个属性组成的 DateRange 值对象。然后在我的域对象上,我只有一个 DateRange 类型的属性。 EF 将这些映射到域对象本身的表。

我提出这个问题是因为回到我们为将 Id 添加到 ValueObject&lt;T&gt; 基本类型所做的让步,即使它的 Id 可能没有在您的域对象具体实现中列出,它仍然存在并且仍然会被选中由 Entity Framework 提供,这可能是最常见的值对象用例不再工作得那么好。

好的,最后,谈谈你的具体情况(我也遇到过几次)。以下是我选择处理实体需要包含值对象列表的方式。基本上它归结为扩大我们对领域的理解。假设 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 };
        }
    }

这将允许您的 BlogEntry 包含多个标签而不会影响值对象,并且 Entity Framework 会很好地映射它而无需执行任何特殊操作。

【讨论】:

  • 最近我喜欢的另一种技术是将值对象的集合存储为 Json 字符串。包含集合的实体对象可以处理输入和输出字符串的解析,以便模型的其余部分仍将其作为集合使用,但对于存储,它作为简单字符串输入。如果您需要手动查询,SQL Server 也可以处理 Json 数据,尽管它需要做更多的工作。作为奖励,如果您的视图使用 Ajax/Jquery,您可以按原样使用 JsonString 属性。
  • 我能问一下:1-为什么我们只使用Tag 作为Entity 而不是VO,因为正如你所解释的,我们必须创建一个无用的实体作为真实VO 的包装器! 2-我们可以在PostTag中添加一个外键-->BlogEntry的id吗? 3-EF是否通过创建两个1-m的表来处理这种情况?
  • 如果它真的像标签一样简单,并且该标签只用于帖子,那么是的,那样也可能没问题。然而这里的想法是标签可能是承载更复杂逻辑的东西,如果你有一堆使用标签的东西,你将不得不每次都复制该逻辑而不是维护一个标签 VO。
【解决方案3】:

我认为了解那些使用 Entity Framework Core 2.0 的人可能会有用

在要由实体框架 (EF) 使用的类中没有 ID 字段是 在 EF Core 2.0 之前不可能,这极大地有助于实现 没有 ID 的更有价值的对象。

以下是 Microsoft 关于此功能的详细信息:

在 EF Core 2.0 及更高版本中将值对象保留为拥有的实体类型 即使在 DDD 中的规范值对象模式之间存在一些差距 和 EF Core 中的自有实体类型,这是目前最好的方法 使用 EF Core 2.0 及更高版本保留值对象。

自有实体类型功能自 2.0 版起添加到 EF Core。

拥有的实体类型允许您映射没有它们的类型 在域模型中明确定义的自己的身份,并用作 任何实体中的属性,例如值对象。一个 拥有的实体类型与另一个实体类型共享相同的 CLR 类型 (也就是说,它只是一个普通的类)。包含的实体 定义导航是所有者实体。询问车主时, 默认情况下包含拥有的类型。

仅通过查看域模型,拥有的类型看起来像 没有任何身份。然而,在幕后,拥有的类型确实 有身份,但所有者导航属性是其中的一部分 身份。

拥有类型的实例的身份并不完全是它们自己的。 它由三个部分组成:

-所有者的身份

-指向它们的导航属性

-在拥有类型的集合的情况下,一个独立的组件(在 EF Core 2.2 及更高版本中受支持)。

按照惯例,会为拥有的类型创建一个影子主键,并且 它将通过使用表映射到与所有者相同的表 分裂。这允许使用拥有的类型,类似于复杂程度 类型在传统 .NET Framework 的 EF6 中使用。

需要注意的是,拥有的类型永远不会被 EF Core 中的约定,因此您必须明确声明它们。

有关自有实体类型的其他详细信息

-当您使用 OwnsOne fluent API 将导航属性配置为特定类型时,会定义拥有的类型。

-在我们的元数据模型中,拥有类型的定义是由以下各项组成的:所有者类型、导航属性和 CLR 类型 拥有的类型。

-我们堆栈中拥有类型实例的身份(键)是所有者类型的身份和定义的组合 拥有的类型。

自有实体功能

- 拥有的类型可以引用其他实体,无论是拥有的(嵌套拥有的类型)还是非拥有的(常规引用导航属性到其他实体) 实体)。

-您可以通过单独的导航属性将相同的 CLR 类型映射为同一所有者实体中的不同拥有类型。

-表拆分是按约定设置的,但您可以通过使用 ToTable 将拥有的类型映射到不同的表来选择退出。

-对拥有的类型自动执行预加载,也就是说,不需要在查询中调用 .Include()。

-可以使用属性 [Owned] 进行配置,使用 EF Core 2.1 及更高版本。

-可以处理拥有类型的集合(使用 2.2 及更高版本)。

自有实体限制

-您无法创建自有类型的 DbSet(按设计)。

-您不能在拥有的类型上调用 ModelBuilder.Entity()(目前是设计使然)。

-不支持与所有者在同一个表中映射的可选(即可为空)拥有类型(即使用表 分裂)。这是因为对每个属性都进行了映射,我们 对整个空复值没有单独的标记。

- 不支持拥有类型的继承映射,但您应该能够映射具有相同继承层次结构的两个叶类型 不同的拥有类型。 EF Core 不会对以下事实进行推理 它们是同一层次结构的一部分。

与 EF6 的复杂类型的主要区别

-表拆分是可选的,也就是说,它们可以选择映射到单独的表,并且仍然是拥有的类型。

-它们可以引用其他实体(也就是说,它们可以作为与其他非拥有类型的关系的依赖方)。

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 2014-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    相关资源
    最近更新 更多