首页 > 解决方案 > 如何使用 ValueObjects 定义嵌套模型关系

问题描述

我有一个父子关系,其中父级有一个 ValueObject,我无法确定如何正确定义该关系。

为子/父关系添加迁移失败并出现错误...

实体类型“地址”需要定义主键。

以下是当前的代码结构。

public class Address
{
        [Required]
        public string BuildingNumber { get; private set; }
        
        // other address properties...
}
public class Parent
{
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; protected set; }

        [Required]
        public Address PrimaryAddress { get; private set; }
}
public class ParentContext : DbContext
{
    public ParentContext(DbContextOptions<ParentContext> options) : 
     base(options)
    {
    }

    public DbSet<Parent> Parents { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress);

        // Flatten the ValueObject fields into table
        modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress).
            Property(b => b.BuildingNumber).IsRequired().
                HasColumnName("Primary_BuildingName");
     }
}
public class Child
{
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; protected set; }

        [Required]
        public int ParentId { get; private set; }

        [ForeignKey("ParentId")]
        public Parent Parent { get; private set; }
}
public class ChildContext : DbContext
{
    public ChildContext(DbContextOptions<ChildContext> options) : base(options)
    {
    }

    public DbSet<Child> Children { get; set; }
}

使用上面的代码示例,我可以运行单独的命令来为 Parent 和 Child 创建迁移,并且表格看起来正确。

add-migration create-parent -c parentcontext

add-migration create-child -c childcontext

添加到实体的关系并添加最终迁移失败。

add-migration add-parent-child-fk -c childcontext

只有当我在不同的上下文中有子项和父项时才会出现问题。

我已经尝试在父项和子项中以不同的方式定义关系以映射地址字段,以便子项“理解”映射,但我无法避免任何我尝试过的 EF 错误。

示例项目在这里

https://github.com/cimatt55/ef-parent-valueobject

标签: c#entity-relationshipef-core-2.2value-objects

解决方案


主要问题是不同的上下文。值对象(拥有的实体类型)只是一个结果 - 如果没有值对象,那么您将遇到另一个问题。

您似乎将您的设计基于一个错误的假设,即只有公开暴露的实体类DbSet。但事实并非如此。还包括导航属性引用的实体,以及它们引用的实体等。

这是合乎逻辑的,因为 EF Core 上下文表示具有表和关系的数据库。EF Core 需要了解所有相关实体才能正确支持加载相关数据、查询(连接)、级联删除、表、列、主键和外键属性/列及其映射等。

EF Core 文档的包含和排除类型部分对此进行了说明:

DbSet按照惯例,在您的上下文的属性中公开的类型包含在您的模型中。此外,OnModelCreating方法中提到的类型也包括在内。最后,通过递归探索已发现类型的导航属性找到的任何类型也包含在模型中。

为您的 调整他们的示例ChildContext,发现以下类型:

  • Child因为它暴露在DbSet上下文的属性中
  • Parent因为它是通过Child.Parent导航属性发现的
  • Address因为它是通过Parent.PrimaryAddress导航属性发现的

由于ChildContext没有Parent实体配置,EF 假定与Parent(and Address) 相关的所有内容都符合约定,因此例外。

Shorty,使用包含相关实体的单独上下文不是一个好主意。解决方案是将所有相关实体放在一个上下文中并对其进行维护。

查看使用的术语,您可能已经在 DDD 和有界上下文之后,但这些不适合 EF Core(通常在关系数据库中)模型。


推荐阅读