asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型)
一. 支持字段
EF允许读取或写入字段而不是一个属性。在使用实体类时,用面向对象的封装来限制或增强应用程序代码对数据访问的语义时,这可能很有用。无法使用数据注释配置。除了约定,还可以使用Fluent API为属性配置支持字段。
1.1 约定
public class Blog { // _<camel-cased property name> private string _url; public int BlogId { get; set; } public string Url { get { return _url; } set { _url = value; } } }
1.2 Fluent API
modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasField("_validatedUrl"); public class Blog { private string _validatedUrl; public string Url { get { return _validatedUrl; } } public void SetUrl(string url) { //... _validatedUrl = url; } }
二. 构造函数
从开始 EF Core 2.1,可以定义带参数的构造函数,并在创建实体实例时让EF Core调用此构造函数。构造函数参数可以绑定到映射属性,或绑定到各种服务,以方便延迟加载等行为。
2.1 带参的构造函数
下面代码演示带参数的构造函数,并且设置只读属性,外部调用该类时,只能通过构造函数传入实体值。
public class Blog { public Blog(int id, string name, string author) { Id = id; Name = name; Author = author; } public int Id { get; private set; } public string Name { get; private set; } public string Author { get; private set; } public ICollection<Post> Posts { get; } = new List<Post>(); }
别外使用私有setter的另一种方法是使属性真正只读,并在OnModelCreating中添加更明确的映射。
public class Blog { private int _id; public Blog(string name, string author) { Name = name; Author = author; } public string Name { get; } public string Author { get; } public ICollection<Post> Posts { get; } = new List<Post>(); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>( b => { b.HasKey("_id"); b.Property(e => e.Author); b.Property(e => e.Name); }); }
2.2 注入服务
EF Core还可以将“服务”注入实体类型的构造函数中。例如,可以注入以下内容:
DbContext - 当前上下文实例,也可以作为派生的DbContext类型键入
ILazyLoader- 延迟加载服务
Action<object, string>- 一个延迟加载的委托
IEntityType - 与此实体类型关联的EF Core元数据
例如,注入的DbContext可用于选择性地访问数据库以获得关于相关实体的信息而无需全部加载它们。在下面的示例中,这用于获取Blog博客中的Posts帖子数量:
public class Blog { public Blog() { } private Blog(BloggingContext context) { Context = context; } private BloggingContext Context { get; set; } public int Id { get; set; } public string Name { get; set; } public string Author { get; set; } public ICollection<Post> Posts { get; set; } //获取帖子数量 public int PostsCount => Posts?.Count ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId")) ?? 0; }
有一些需要注意:
(1)构造函数是私有的,因为它只由EF Core调用,并且还有另一个通用的公共构造函数。
(2)使用注入服务的代码(即EF上下文)防御它为null,处理EF Core未创建实例的情况。
(3)因为服务存储在读或写属性中,所以当实体附加到新的上下文实例时,它将被重置。
2.2示例演示,没有成功,Blog带参的构造函数为私有,无法调用, context上下文总为null,以后在了解。
三.拥有的实体类型
该功能是在 EF Core 2.0 中的新增功能。是指:当一个实体类中包含导航属性(实体类型引用属性),并对导航属性进行建模,这个导航属性类被称为“拥有实体类型”。而包含“拥有实体类型”的类叫:所有者。
3.1 显示配置
EF Core中的所有实体类型永远不会按照约定包含在模型中。可以使用OwnsOne
在(使用EF Core 2.1中的新增功能)OnModelCreating
中使用或用注释类型OwnedAttribute
将类型配置为拥有类型。
下面示例中StreetAddress
是一个没有标识属性的类型。 它用作 Order 类型的属性来指定特定订单的发货地址。
//拥有实体类型 [Owned] public class StreetAddress { public string Street { get; set; } public string City { get; set; } } //所有者 public class Order { public int Id { get; set; } public StreetAddress ShippingAddress { get; set; } } public class BloggingContext : DbContext { public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { } public DbSet<Order> Order { get; set; }
}
使用EF基于数据模型(Order)创建数据库,如下图所示。
还可以使用该OwnsOne
方法在OnModelCreating
中
指定ShippingAddress
属性,是Order
实体类型的拥有实体,并根据需要配置其他方面。
modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);
如果ShippingAddress
属性在Order
实体中为私有属性,则可以使用的字符串版本OwnsOne
方法:
modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");
3.2 隐含键
OwnsOne通过引用导航配置或发现的拥有类型始终与所有者具有一对一的关系,因此拥有实体不需要自己的键值,因为外键值是唯一的。在前面的示例中,StreetAddress
类型不需要定义键属性。拥有实体类型的实例键值与所有者实例的键值相同。
3.3 拥有的集合类型
要配置拥有的集合类型,使用OwnsMany
在OnModelCreating
中使用。
但是,主键不会按约定配置,因此需要明确指定。下面代码演示拥有的集合类型的关键代码。
public class Distributor { public int Id { get; set; } public ICollection<StreetAddress> ShippingCenters { get; set; } } modelBuilder.Entity<Distributor>().OwnsMany(p => p.ShippingCenters, a => { // 给ShippingCenters表设置一个主键 a.Property<int>("Id"); //给ShippingCenters表设置一个外键 a.HasForeignKey("DistributorId"); //设置复合主键 a.HasKey("DistributorId", "Id"); });
使用EF基于数据模型(Distributor)创建数据库,如下图所示(一对多的关系)。Distributor_ShippingCenters表的ID为主键,DistributorId为外键。
设计限制:
对于拥有的实体类型无法创建DbSet<T>。
在 ModelBuilder中调用Entity<T>()时,T不能是拥有的实体类型。
关于拥有的实体类型的更多介绍参考官方文档,这里不是介绍。关于“EF模型配置系列”的很多功能,都是基于code first模式。
参考文献:
官方文档:EF支持字段