具有构造函数的实体类型Entity types with constructors

备注

此功能是 EF Core 2.1 中的新增功能。

从开始 EF Core 2.1,它则现在可以定义参数的构造函数,并让 EF Core 创建实体的实例时调用此构造函数。 构造函数参数可以绑定到映射的属性或各种类型的服务,以促进延迟加载等行为。

备注

截至 EF Core 2.1,按照约定将为所有构造函数绑定。 计划在将来的版本中配置要使用的特定构造函数。

绑定到映射的属性Binding to mapped properties

请考虑典型的博客/公告模型:

  1. public class Blog
  2. {
  3. public int Id { get; set; }
  4. public string Name { get; set; }
  5. public string Author { get; set; }
  6. public ICollection<Post> Posts { get; } = new List<Post>();
  7. }
  8. public class Post
  9. {
  10. public int Id { get; set; }
  11. public string Title { get; set; }
  12. public string Content { get; set; }
  13. public DateTime PostedOn { get; set; }
  14. public Blog Blog { get; set; }
  15. }

当 EF Core 创建这些类型的实例时,如的结果的查询,它将首先调用默认的无参数构造函数,然后设置每个属性的值从数据库。 但是,如果 EF Core 查找的参数化构造函数参数名称和类型相匹配的映射属性,则它将改为调用这些属性具有值的参数化构造函数,然后将未显式设置每个属性。 例如:

  1. public class Blog
  2. {
  3. public Blog(int id, string name, string author)
  4. {
  5. Id = id;
  6. Name = name;
  7. Author = author;
  8. }
  9. public int Id { get; set; }
  10. public string Name { get; set; }
  11. public string Author { get; set; }
  12. public ICollection<Post> Posts { get; } = new List<Post>();
  13. }
  14. public class Post
  15. {
  16. public Post(int id, string title, DateTime postedOn)
  17. {
  18. Id = id;
  19. Title = title;
  20. PostedOn = postedOn;
  21. }
  22. public int Id { get; set; }
  23. public string Title { get; set; }
  24. public string Content { get; set; }
  25. public DateTime PostedOn { get; set; }
  26. public Blog Blog { get; set; }
  27. }

需要注意的事项:

  • 并非所有属性都需要具有构造函数参数。 例如,由任何构造函数参数,因此 EF Core 将以正常方式调用的构造函数后设置未设置 Post.Content 属性。
  • 参数类型和名称必须与属性类型和名称相匹配,但在参数采用 camel 大小写格式时,属性可以采用 Pascal 大小写形式。
  • 无法设置 (如博客或更高版本的文章) 的导航属性,EF Core 使用构造函数。
  • 构造函数可以是公共的,也可以是私有的,或者具有任何其他可访问性。 不过,延迟加载代理要求构造函数可从继承代理类访问。 通常,这意味着将其设为公共或受保护。

只读属性Read-only properties

通过构造函数设置属性后,就可以使某些属性成为只读的。 EF Core 支持此功能,但有一些需要注意的事项:

  • 不会按约定映射没有 setter 的属性。 (这样做常常会映射不应映射的属性,例如计算属性。)
  • 使用自动生成的键值需要键属性,该属性是读写的,因为在插入新实体时密钥生成器需要设置密钥值。

避免这种情况的简单方法是使用专用资源库。 例如:

  1. public class Blog
  2. {
  3. public Blog(int id, string name, string author)
  4. {
  5. Id = id;
  6. Name = name;
  7. Author = author;
  8. }
  9. public int Id { get; private set; }
  10. public string Name { get; private set; }
  11. public string Author { get; private set; }
  12. public ICollection<Post> Posts { get; } = new List<Post>();
  13. }
  14. public class Post
  15. {
  16. public Post(int id, string title, DateTime postedOn)
  17. {
  18. Id = id;
  19. Title = title;
  20. PostedOn = postedOn;
  21. }
  22. public int Id { get; private set; }
  23. public string Title { get; private set; }
  24. public string Content { get; set; }
  25. public DateTime PostedOn { get; private set; }
  26. public Blog Blog { get; set; }
  27. }

EF Core 看到为读写模式,这意味着,像以前那样映射所有属性并密钥可能仍会由存储生成的具有专用 setter 的属性。

使用专用资源库的一种替代方法是使属性真正为只读,并在 OnModelCreating 中添加更多显式映射。 同样,可以完全删除某些属性并仅替换为字段。 例如,请考虑以下实体类型:

  1. public class Blog
  2. {
  3. private int _id;
  4. public Blog(string name, string author)
  5. {
  6. Name = name;
  7. Author = author;
  8. }
  9. public string Name { get; }
  10. public string Author { get; }
  11. public ICollection<Post> Posts { get; } = new List<Post>();
  12. }
  13. public class Post
  14. {
  15. private int _id;
  16. public Post(string title, DateTime postedOn)
  17. {
  18. Title = title;
  19. PostedOn = postedOn;
  20. }
  21. public string Title { get; }
  22. public string Content { get; set; }
  23. public DateTime PostedOn { get; }
  24. public Blog Blog { get; set; }
  25. }

OnModelCreating 中的此配置:

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. modelBuilder.Entity<Blog>(
  4. b =>
  5. {
  6. b.HasKey("_id");
  7. b.Property(e => e.Author);
  8. b.Property(e => e.Name);
  9. });
  10. modelBuilder.Entity<Post>(
  11. b =>
  12. {
  13. b.HasKey("_id");
  14. b.Property(e => e.Title);
  15. b.Property(e => e.PostedOn);
  16. });
  17. }

注意事项:

  • “属性” 键现在是字段。 它不是 readonly 字段,因此可以使用存储生成的键。
  • 其他属性是仅在构造函数中设置的只读属性。
  • 如果主键值只是由 EF 设置或从数据库中读取,则无需将其包含在构造函数中。 这会使 “属性” 键作为一个简单字段,并清楚地指出不应在创建新的博客或文章时显式设置。

备注

此代码将导致编译器警告 “169”,指示该字段从未使用过。 由于在现实中 EF Core extralinguistic 的方式使用该字段,这可予以忽视。

注入服务Injecting services

EF Core 还可以将”服务”注入到实体类型的构造函数。 例如,可以注入以下内容:

  • DbContext-当前上下文实例,也可以类型化为派生的 DbContext 类型
  • ILazyLoader-延迟加载服务-有关更多详细信息,请参阅延迟加载文档
  • Action<object, string>-延迟加载委托—有关更多详细信息,请参阅延迟加载文档
  • IEntityType-与此实体类型关联的 EF Core 元数据

备注

截至 EF Core 2.1,可插入仅通过 EF Core 已知的服务。 将来的版本会考虑对注入应用程序服务的支持。

例如,注入的 DbContext 可用于有选择地访问数据库,以获取相关实体的相关信息,而无需将它们全部加载。 在下面的示例中,这用于在不加载帖子的情况下获取博客中的帖子数:

  1. public class Blog
  2. {
  3. public Blog()
  4. {
  5. }
  6. private Blog(BloggingContext context)
  7. {
  8. Context = context;
  9. }
  10. private BloggingContext Context { get; set; }
  11. public int Id { get; set; }
  12. public string Name { get; set; }
  13. public string Author { get; set; }
  14. public ICollection<Post> Posts { get; set; }
  15. public int PostsCount
  16. => Posts?.Count
  17. ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId"))
  18. ?? 0;
  19. }
  20. public class Post
  21. {
  22. public int Id { get; set; }
  23. public string Title { get; set; }
  24. public string Content { get; set; }
  25. public DateTime PostedOn { get; set; }
  26. public Blog Blog { get; set; }
  27. }

请注意以下几点:

  • 构造函数是专用容器,因为它只能由 EF Core,并且没有用于常规用途的另一个公共构造函数。
  • 使用注入的服务(即上下文)的代码可防御 null 处理 EF Core 不创建实例的情况。
  • 因为服务存储在读/写属性中,所以当将实体附加到新的上下文实例时,它将被重置。

警告

因为它将直接与 EF Core 的实体类型,将注入如下 DbContext 通常被视为反模式。 使用此类服务注入之前,请仔细考虑所有选项。