具有构造函数的实体类型
可以使用参数定义构造函数,并在创建实体实例时使 EF Core 调用此构造函数。 构造函数参数可以绑定到映射的属性或各种类型的服务,以促进延迟加载等行为。
注意
目前,所有构造函数绑定都按约定进行。 计划在将来的版本中配置可供使用的特定构造函数。
绑定到映射的属性
考虑典型的 Blog/Post 模型:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime PostedOn { get; set; }
public Blog Blog { get; set; }
}
当 EF Core 创建这些类型的实例(例如查询结果)时,它将首先调用默认的无参数构造函数,然后将每个属性设置为数据库中的值。 但是,如果 EF Core 找到参数名称和类型与映射的属性相匹配的参数化构造函数,它将改为使用这些属性的值调用参数化构造函数,并且不会显式设置每个属性。 例如:
public class Blog
{
public Blog(int id, string name, string author)
{
Id = id;
Name = name;
Author = author;
}
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public Post(int id, string title, DateTime postedOn)
{
Id = id;
Title = title;
PostedOn = postedOn;
}
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime PostedOn { get; set; }
public Blog Blog { get; set; }
}
需要注意的一些事项:
- 并非所有属性都需要具有构造函数参数。 例如,Post.Content 属性不是由任何构造函数参数设置的,因此 EF Core 会在以正常方式调用构造函数后对其进行设置。
- 参数类型和名称必须与属性类型和名称相匹配,但在参数采用 camel 大小写格式时,属性可以采用 Pascal 大小写形式。
- EF Core 无法使用构造函数设置导航属性(例如上文中的 Blog 或 Posts)。
- 构造函数可以是公共的、专用的或具有任何其他可访问性。 不过,延迟加载代理要求构造函数可从继承代理类访问。 通常,这意味着需要将其设为公共或受保护。
只读属性
通过构造函数设置属性后,就可以将某些属性设置为只读。 EF Core 支持此功能,但需要注意以下事项:
- 按照约定,不会映射没有资源库的属性。 (这样做通常是为了映射不应映射的属性,例如计算属性。)
- 使用自动生成的键值需要键属性,该属性是读写的,因为在插入新实体时密钥生成器需要设置密钥值。
避免这种情况的简单方法是使用专用资源库。 例如:
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>();
}
public class Post
{
public Post(int id, string title, DateTime postedOn)
{
Id = id;
Title = title;
PostedOn = postedOn;
}
public int Id { get; private set; }
public string Title { get; private set; }
public string Content { get; set; }
public DateTime PostedOn { get; private set; }
public Blog Blog { get; set; }
}
EF Core 将具有专用资源库的属性视为可读写,这意味着所有属性都将像以前一样映射,并且键仍然可以存储生成。
使用专用资源库的一种替代方法是使属性真正为只读,并在 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>();
}
public class Post
{
private int _id;
public Post(string title, DateTime postedOn)
{
Title = title;
PostedOn = postedOn;
}
public string Title { get; }
public string Content { get; set; }
public DateTime PostedOn { get; }
public Blog Blog { get; set; }
}
以及 OnModelCreating 中的此配置:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(
b =>
{
b.HasKey("_id");
b.Property(e => e.Author);
b.Property(e => e.Name);
});
modelBuilder.Entity<Post>(
b =>
{
b.HasKey("_id");
b.Property(e => e.Title);
b.Property(e => e.PostedOn);
});
}
注意事项:
- 键“属性”现在为字段。 它不是一个
readonly
字段,因此可以使用存储生成的键。 - 其他属性是仅在构造函数中设置的只读属性。
- 如果主键值是仅由 EF 设置或从数据库中读取的,则无需将其包含在构造函数中。 这会将键“属性”作为一个简单字段,并清楚地指出不应在创建新的博客或帖子时显式设置。
注意
此代码将导致编译器警告“169”,指示该字段从未使用过。 可以忽略该警告,因为在现实中 EF Core 以超语言的方式使用该字段。
注入服务
EF Core 还可以将“服务”注入实体类型的构造函数中。 例如,可以注入以下内容:
DbContext
- 当前上下文实例,也可以类型化为派生的 DbContext 类型ILazyLoader
- 延迟加载服务 - 有关详细信息,请参阅加载文档Action<object, string>
- 延迟加载服务 - 有关详细信息,请参阅延迟加载文档IEntityType
- 与此实体类型关联的 EF Core 元数据
注意
目前只能注入 EF Core 已知的服务。 将来的版本中会考虑对注入应用程序服务的支持。
例如,注入的 DbContext 可用于选择性地访问数据库,以获取相关实体的信息,而无需加载全部实体。 在下面的示例中,这用于在不加载帖子的情况下获取博客中的帖子数:
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;
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime PostedOn { get; set; }
public Blog Blog { get; set; }
}
请注意以下几点:
- 构造函数是专用的,因为它只由 EF Core 调用,另一个公共构造函数用于常规用途。
- 使用注入服务的代码(即上下文)防止它为
null
以处理 EF Core 未创建实例的情况。 - 由于服务存储在读/写属性中,当将实体附加到新的上下文实例时,它将被重置。
警告
注入此类 DbContext 通常被视为一种反模式,因为它会将实体类型直接耦合到 EF Core。 使用此类服务注入之前,请仔细考虑所有选项。