2017 年 1 月

第 32 卷,第 1 期

此文章由机器翻译。

数据点 - EF Core 1.1: 我喜欢的一些事项

作者:Julie Lerman

Julie Lerman2016 年 11 月我写这个专栏时,Entity Framework (EF) Core 1.1 才刚刚发布。在 1.0 版本和 1.1 版本之间,发生了一些重要的事情。尤其是 1.0.1 修补程序修复了一些在发布 1.0 时发现的关键 bug。有关这些修复内容的详细列表,你可以参阅 bit.ly/2fl5xNE 上的发行说明。虽然其中大多数修复与 LINQ 查询如何转换为 SQL 有关,但也有一些修复涉及模型生成器、通过 SaveChanges 和迁移的持久性。

EF Core 1.1 带来了额外的 Bug 修复、对如何解释 LINQ 查询的更多改进以及重大的性能提升。但也有一些新功能,即存在于 EF6 但尚未引入 EF Core 1.0 的一些功能,许多开发人员在 EF Core 中甚至不能查看这些功能,此外还有其他一些全新功能。我想在本文快速列出这些变化,然后重点介绍一些我非常感兴趣的内容。我还将提供指向许多有价值的资源的链接,这些资源帮助我努力跟上了 EF Core 的发展步伐。首先,文档 (docs.microsoft.com/ef) 和团队博客文章 (bit.ly/2ehZBUB) 中提供了大量的信息。

特别是,你应该知道 Arthur Vickers 的博客。Arthur 是一名高级开发人员,从 EF 开始提供 POCO 支持以来一直在该团队工作,他在 EF 的现代化方面发挥了重要作用。他在 blog.oneunicorn.com 上撰写了很多有关 EF Core 1.1 的帖子。

Microsoft 计划在 EF Core 中引入许多 EF6 功能,但并非全部。还计划在未来版本中增加一些新功能。现在,EF Core 1.1 专注于使 EF Core 满足更多开发人员的需求的升级。下面介绍其中重要的功能。

EF 1.1 现在提供的 EF6 功能

DbSet.Find 方法随着 DbContext API 引入 EF 4.1 中而变得可用,但并没有列入 EF Core 第一次迭代的点击列表中,这让很多开发人员不满意。Find 不仅仅是一种基于键值有效查询实体的简便方法。它首先通过检查来查看实体是否已经在内存中并且正在被 EF 跟踪。如果在 DbContext 缓存中找不到实体,EF 将对数据库执行 FirstOrDefault 查询以检索实体。这在 EF6 和更早版本中称作 SingleOrDefault 查询。Find 的设计避免了不必要的数据库访问,这也是一个性能优势。如果你像我一样想看到底层命令,可以在 GitHub (bit.ly/2e9V0Uu) 的 EntityFinder 类中查找代码。

另一个大的区别是 Find 现在包含在 EntityFinder 服务中;它在 EF6 中不只是一种在内部 DbSet 类中直接实现的方法。EF Core 由数百种服务组成,用于封装特定任务。Rowan Miller 在有关 EF Core 1.1 (bit.ly/2fHOCsN) 的第 9 频道视频演示中,展示了如何直接使用服务以及如何替换它们。

作为具有 1.1 更新的 EF Core 的一部分,EF6 功能被称为连接弹性,它支持轻松处理在远程数据库(如 Azure SQL 数据库)上运行时可能发生的暂时连接问题。如果你使用 ASP.NET Core 依赖关系注入,则可以在 DbContext.OnConfiguring 中或者在 Startup.cs 的 AddDbcontext 调用中设置 SQL 数据库提供程序的 EnableRetryOnFailure 扩展方法。EnableRetryOnFailure 调用 SqlServerRetryingExecutionStrategy,后者继承自 ExecutionStrategy 类型。可以创建和设置自己的 ExcecutionStrategy,并且其他提供程序也可以预定义自己的 ExecutionStrategy 配置。如果你对该功能不熟悉,它类似于 EF6 DbExecutionStrategy,我在 Pluralsight 课程 “EF6 Ninja Edition” (bit.ly/PS_EF6) 中进行过深入介绍。

EF 一直都提供三种加载相关数据的方式。一种是预先加载,由 DbSet.Include 方法启用来在单个查询中检索数据图形。相比之下,另外两种方式是,主要对象已在内存中之后而跟踪它们的 DbContext 仍在范围内时加载相关的数据。延迟加载根据需要自动拉入相关数据,而显式加载时需显式告知 EF 加载相关数据。Include 是最先在 EF Core 中实现的方法之一,并且随 EF Core 1.0 一起提供。它甚至从 EF6 和更早版本起就对版本进行一些不错的改进。使用 Load 方法的显式加载现在已添加到 EF Core 1.1 中。现在尚不支持延迟加载,但过段时间将会支持。

通过 DbContext 的更改跟踪器 API,你可以直接访问更改跟踪器信息,例如使用 DbContext.Entry(someObject).State 方法获取或设置实体的状态。EF6 使用 GetDatabaseValues、CurrentValues、OriginalValues 等新方法引入了额外的控制。现在 EF Core 1.1 已提供这些方法。

EF 1.1 中的新功能

EF Core 包含早期版本从未提供的功能。下面是一个简短的示例列表:SaveChanges 期间的批处理、唯一的外键、用于测试的出色的 InMemory 提供程序、更智能的 LINQ 查询处理以及更智能和更简单的流畅映射。

EF 1.1 带来了一些额外的新功能,作为领域驱动设计 (DDD) 的狂热粉丝,其中有一个特别功能我很喜欢,那就是支持封装的集合。

映射的字段和封装的集合 EF Code First 仅支持映射到同时具有 getter 和 setter 的属性,即使 setter 是专用的。对于集合导航,属性必须是 ICollection。如果要限制属性值的填充方式,则封装属性的功能至关重要:通过这种方式,你可以强制使用你的类的任何人使用公共方法,以确保遵守有关该属性的业务规则。EF6 和更早版本允许你通过将 setter 设置为专用来封装标量属性。但是没有一种办法能真正封装集合并防止任何人直接修改集合。

使用 EF 1.1,可以直接映射到字段以及 IEnumerable 属性。这种新功能不仅可以映射到属性,还能映射到字段,让你能够使用比隐藏 setter 更直接的方法,并且还支持封装标量值的一些其他方法。这里有一个属性 DueDate,它包含 getter 但没有 setter,还包含绑定到该属性的字段 _dueDate:

private DateTime _dueDate;
public DateTime DueDate {
  get { return _dueDate;
  }
}

设置 DueDate 的唯一方法是调用可修改字段的 CalculateDueDate 方法:

private void CalculateDueDate() {
  _dueDate=Start.AddDays(DaysLoaned);
}

EF Core 1.1 需要在 DbContext 中进行显式映射,以通知 EF 它可以使用 _dueDate 字段映射到数据库,例如在返回查询结果时。必须使用 Property(以及可选的 HasField)API 方法指定 _dueDate 字段是 DueDate 属性的替代项。在这种情况下,因为字段名称 _dueDate 遵循 EF 约定,我不必使用 HasField 方法,但我已经添加它以便你可以看到:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
  modelBuilder.Entity<BookLoan>().Property(bl => bl.DueDate)
  .HasField(“_dueDate”);
}

虽然你可以在字段映射可用之前使用专用 setter,但没有办法封装集合以防止任何人通过 Add 或 Remove 方法直接操作集合。这与隐藏 setter 无关;你需要做的是隐藏集合方法。DDD 中的一个常见方法是使该属性成为 IEnumerable。然而,使用 EF6 和更早版本,你只能映射到 ICollection 类型;但 IEnumerable 不是 ICollection。

起初,我看了一下你是否可以使用字段映射功能,但是在 EF 1.1 中这是不可能的,因为它仅支持映射到标量属性。上述内容只针对下一版本的 EF Core 中的更改,该版本允许映射到导航属性。但是有一天我在 Twitter 上指出这点时,Arthur Vickers 告诉我事实上可以映射到 IEnumerable,我漏掉了这个与 EF Core 有关的要点。所以我现在可以完全封装和保护集合,强制我的 API 用户通过一个方法来修改集合。下面我举一个例子,我可以在每次有书籍外借时向书中添加一个新的 BookLoan 实例,确保包含外借期限:

private List<BookLoan> _bookLoans;
public IEnumerable<BookLoan> BookLoans {
  get { return _bookLoans; }
}
public void BookWasLoaned(int daysLoaned){
  _bookLoans.Add(new BookLoan(DateTime.Today, 14));
}

这是通过利用 IEnumerable 映射实现封装的一种模式。但我建议你阅读 Arthur 的博客文章 (bit.ly/2fVzIfN) 了解更多的细节,包括如何在没有支持字段(我的一个 _bookLoans 字段)的情况下执行该任务,设计增强内容和一些要注意的陷阱。

支持特定于数据库的功能 EF Core 旨在使提供程序能够更轻松地支持特定的数据存储功能。例如,EF Core 的 SQL Server 提供程序支持应用程序的 SQL Server 内存优化表,你将能获得令人难以置信的高吞吐量。要指定实体映射到这种特殊类型的表,SQL Server 提供程序有一种扩展方法供你在通过 Fluent API 配置模型时使用:

modelBuilder.Entity<Book>().ForSqlServerIsMemoryOptimized();

这不仅会影响代码首先如何生成表创建脚本,也将影响 EF 如何生成将数据推送到数据库的命令。你可以在前面提到的第 9 频道视频中看到有趣的演示,其中由 Rowan Miller 演示了 EF 1.1 的功能。

Shay Rojansky 为 EF Core 构建了 PostgreSQL 提供程序,他撰写了一篇文章来介绍 EF Core 如何让他支持 PosgreSQL 的特殊功能,例如数组类型。可以在 bit.ly/2focZKQ 上阅读这篇文章。

更轻松地访问服务

正如我前面在使用 EntityFinder 服务时强调的那样,EF Core 由数百种服务组成。在第 9 频道视频中,Rowan 演示了如何在代码中直接访问和使用这些服务。此外,还可以使用自己的自定义来替代服务。EF 1.1 借助一种可以在 OnConfiguring 中使用的简单替换方法让你更轻松地执行该操作。可以将服务替换为从该服务继承或实现相同接口的另一项服务。没有特定的服务列表,这些只是 EntityFrameworkCore 的各种 API 中的类。我举一个简单易懂的示例:创建一个继承自数据库类型映射程序的类,例如 Sqlite 提供程序的 SqlLiteTypeMapper,然后添加一个新的类型映射规则。确保该规则是数据库能够转换的规则。(未来版本的 EF Core 中将提供某些更智能的类型转换。) 然后,在 OnConfiguring 中设置替换规则:

optionsBuilder.ReplaceService<SqliteTypeMapper,MySqliteTypeMapper>();

为什么我不替换 EntityFinder 服务? 首先,因为我喜欢它的工作方式。其次,由于它是一个通用类,这使得创建新版本变得更加复杂,我选择暂时搁置该选项。创建 ReplaceService 以更方便地更换内部服务时,你需要记住 EF Core 内部内容可能会更改,因此你的替换今后可能会产生问题。你可以轻松地识别这些内容,因为它们在命名空间中以单词 Internal 结尾。Microsoft: “我们通常在次要版本和修补程序版本的非 .Internal 命名空间中避免重大更改。”

值得强调(因为它在当时引起了一些争议)的是一个与异步查询相关的性能问题的修复,这些查询使用在 1.0 即将发布时发现的 Include 方法。该问题得到快速解决(如果你想要了解详细信息,请参阅 bit.ly/2faItD1),之后报告性能提高了 70%,该问题也属于 1.1 版本的一部分。

总结

EF Core 有很多复杂的功能。非常重要的是我们要知道 EF Core 有什么功能、没有什么功能、新增什么功能以及永远不会包含什么功能。关于其与 EF6 (bit.ly/2fxbVVj) 的关系的 EF Core 文档也是一个有用的资源。关键是确定 EF Core 在什么时适合你和你的应用。

对于我做的大部分工作,EF Core 1.1 都能提供我需要的 API 和功能。作为一个 DDD 从业者,封装集合的功能对我来说非常重要,虽然我还在等待包含复杂类型的映射,但我也可以在我的模型中使用值对象。此功能适用于下一版本的 EF Core。我最近还发现 EF Core 已经实现了我的愿望 — 定义只读(非跟踪)DbContext,这让我很兴奋。我完全没有注意到这是早期添加的功能,是 EF Core 1.0 版本的一部分。你可以在 bit.ly/2f75l8m 上查阅我的博客文章来进一步了解上述功能。


Julie Lerman是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她的博客地址是 thedatafarm.com/blog。她是“Entity Framework 编程”及其 Code First 和 DbContext 版本(全都出版自 O’Reilly Media)的作者。通过 Twitter 关注她:@julielerman 并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅: Rowan Miller


在 MSDN 杂志论坛讨论这篇文章