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






4.56/5 (4投票s)
使用 EF Core 所有者类型,将您不希望作为引用出现的字段分组到单独的类型中。
我发现实体框架核心的一个非常好的特性,想和大家分享一下,那就是所有者类型。
EF Core 的所有者类型允许您将不希望作为引用出现的字段分组到单独的类型中。
让我们从一个例子开始。假设您有这个模型。您有 Customer
和 Order
,两者都有 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
字段提取到新表并引用 Customer
和 Order
表中的该表来重构之前的模型。
class Address
{
[Key]
public long AddressID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
}
但是,如果您想将三个 address
字段保留在 Customer
和 Order
内部,并且您希望重构代码并减少复杂性和重复,您该怎么办?答案就在所有者实体中。
您可以更新您的 Address
类,删除其键字段,并在 Customer
和 Order
中包含一个引用,如下所示
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日:初始版本