65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Entity Framework Core 拥有的类型减少复杂性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (4投票s)

2021年3月21日

CPOL

2分钟阅读

viewsIcon

6790

使用 EF Core 所有者类型,将您不希望作为引用出现的字段分组到单独的类型中。

我发现实体框架核心的一个非常好的特性,想和大家分享一下,那就是所有者类型。

EF Core 的所有者类型允许您将不希望作为引用出现的字段分组到单独的类型中。

让我们从一个例子开始。假设您有这个模型。您有 CustomerOrder,两者都有 address 字段。

  class Customer
  {
    [Key]
    public long CustomerID { get; set; }
    public string Name { get; set; }

    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  }
  class Order
  {
    [Key]
    public long OrderID { get; set; }
    public bool IsShipped { get; set; }

    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  }

您可以轻松地通过将 address 字段提取到新表并引用 CustomerOrder 表中的该表来重构之前的模型。

  class Address
  {
    [Key]
    public long AddressID { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  }

但是,如果您想将三个 address 字段保留在 CustomerOrder 内部,并且您希望重构代码并减少复杂性和重复,您该怎么办?答案就在所有者实体中。

您可以更新您的 Address 类,删除其键字段,并在 CustomerOrder 中包含一个引用,如下所示

  class Address
  {
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  }
  class Customer
  {
    [Key]
    public long CustomerID { get; set; }
    public string Name { get; set; }

    public Address Address { get; set; }
  }
  class Order
  {
    [Key]
    public long OrderID { get; set; }
    public bool IsShipped { get; set; }

    public Address Address { get; set; }
  }

完成了?还没有。您不能就这样留下您的代码,因为没有主键就不能定义实体。实际上,如果您尝试将更改应用于数据库,将会得到此错误。

我们需要做的下一个更新是在数据上下文本身。我们可以使用 Fluent API 来提示我们的模型将如何工作。

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

      modelBuilder.Entity<Customer>().OwnsOne(a => a.Address);
      modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
    }

现在我们可以将我们的模型应用于数据库并观看神奇的事情发生!

我们的更新看起来很有希望;但是,我们这里有一个小问题,没有人喜欢那些“Address_”前缀!

答案再次在于 Fluent API。

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

      modelBuilder.Entity<Customer>().OwnsOne(a => a.Address, add=>
      {
        add.Property(p => p.Street).HasColumnName(nameof(Address.Street));
        add.Property(p => p.City).HasColumnName(nameof(Address.City));
        add.Property(p => p.State).HasColumnName(nameof(Address.State));
      });
      modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
    }

应用之前的代码后,您可以看到两个生成的表之间的差异

有一点需要提及的是,您可以使用 Ignore 方法排除属性被映射。

add.Ignore(p => p.State);

如果您不喜欢 Fluent API 方法,您可以简单地使用 OwnedAttribute 属性装饰 Address 类,但是,这将不允许您自定义字段名称和其他属性。

  [Owned]
  class Address
  {
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  }

还有一点需要提及的是,您可以使用 ToTable 方法为所有者实体创建一个表

      modelBuilder.Entity<Customer>().OwnsOne(a => a.Address, add=>
      {
        add.Property(p => p.Street).HasColumnName(nameof(Address.Street));
        add.Property(p => p.City).HasColumnName(nameof(Address.City));
        add.Property(p => p.State).HasColumnName(nameof(Address.State));

        add.ToTable("CustomerAddress");
      });

这将会在 Customer 和新生成的 CustomerAddress 之间创建一个一对一关系。

最后一点是,您可以使用 OwnsMany 方法创建一个一对多关系

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

      modelBuilder.Entity<Customer>().OwnsMany(a => a.Address);
      modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
    }

您还需要更新 Customer 实体以匹配一对多关系

  class Customer
  {
    [Key]
    public long CustomerID { get; set; }
    public string Name { get; set; }

    public List<Address> Address { get; set; }
  }

结果如下

历史

  • 2021年3月21日:初始版本
© . All rights reserved.