对实体框架的下一版本的开发正在顺利进行中。我在 2014 年度北美 TechEd 上第一次了解 EF 团队的工作内容,当时项目经理 Rowan Miller 讨论了 Entity Framework 7 (EF7) 的目标并展示了一些早期的信息。
那是我撰写此专栏的 5 个月之前,虽然 EF7 仍处于早期 Alpha 测试阶段,但已经取得了很大的进展。在本专栏中,我希望您能了解到 EF7 会给开发人员带来什么、有关 EF7 的决定背后的动机以及此版本对使用 EF6 或更低版本的现有应用的意义。我还会向您展示其中的部分代码。
开放源代码,但是现在位于 GitHub 上
首先请了解,EF7 和 EF6 一样,是开放源代码。但 EF7 以及 ASP.NET 的即将发布版本的剩余部分是在 GitHub 上而不是在 CodePlex 上开发的。EF7 开发的 URL 为 github.com/aspnet/EntityFramework。和 EF6 一样,您可以查看 EF7 发展过程的详细信息。您可以通过分支和提交探索源代码及其进度、关注讨论、提出问题、派生源代码以及提交团队的 pull 请求以进行检查并在可能时提交到代码库。
EF6 不会很快退出历史舞台
切勿担心,不会强制您迁移到 EF7。回想一下 ADO.NET DataSets 和 DataReaders。与仍受支持甚至从偶尔的调整中受益的 ASP.NET Web 窗体非常像,ADO.NET 仍然是 Microsoft .NET Framework 的一部分,即使多年以来 EF 已经成为 .NET 的主要数据访问技术。尽管已不再为增强这些技术采取任何措施,但它们仍然存在,并且支持很多旧代码(包括我的代码)。与其他技术相比,EF6 的一个巨大优势在于它是开放源代码,因此尽管 Microsoft 团队已不再对 EF6 做大型投资,但社区仍会进行投资。而且,EF 团队仍在致力于 EF6。他们将继续进行调整,仔细检查 pull 请求,并更新 EF6。尽管他们在 2014 年的大部分时间里全身心地致力于 EF7,但也更新了 EF6。2014 年 2 月发布了版本 6.1.0;2014 年 6 月发布了版本 6.1.1;我在撰写本文时,版本 6.1.2 正处于测试中,即将发布。我最初很担心旧版本的应用是否能继续运行,但现在不再有此顾虑。我现在只担心那些结合使用 EF 和 .NET Framework 3.5、ObjectContext 等程序的最低版本的应用。但是,如果您尚未更新这些应用以使用这些年来对 EF 做出的所有改进,则无需过多担心 EF7。您可以在 NuGet 上找到 EF 的所有较低版本的包,可以一直追溯到 EF 4.1.10311。
EF7:简短列表
EF7 新增功能的高级概述如下:
- 支持非关系数据存储,甚至内存中数据测试。
- 支持未使用完整 .NET Framework 的计算机和设备。这表示,您可以在 Windows Phone 和 Windows 应用商店应用以及运行 Mono 的 Linux 和 Macintosh 计算机上使用 EF7。
- 支持开发人员已请求但无法通过现有代码库实现的许多功能。
- 继续支持使用完整 .NET Framework 的应用程序,例如 Windows Presentation Foundation 和其他客户端应用程序。
- EF7 的分发方式与 ASP.NET 5 一样,并且可以和 ASP.NET 5 应用配合使用。
熟悉的编码界面,新的代码库
EF 的每个版本都在不断完善框架,添加新功能,并对性能和 API 进行微调。正如以前我在本专栏中以及在 2013 年 12 月发布的概述文章《Entity Framework 6:The Ninja Edition》(bit.ly/1cwjyCD) 中写道,最新版本的 EF 已经进入一个新的级别,框架新增了很多用户梦寐以求的功能,例如异步数据库执行、使用查询管道以及自定义代码优先约定等。在我的 Pluralsight 课程“Entity Framework 6, Ninja Edition:EF6 中的新功能”(bit.ly/PS-EF6) 中,我更深入讨论了这些功能。
还有更多功能是开发人员希望在 EF 中找到的且 Microsoft 希望实现的,但用于构建 EF 的已 10 多年的代码库仍继续依赖 ObjectContext,且编码模式不够灵活,因此阻碍了该团队开发更高级的功能。因此做出了一个艰难的决定,即从头开始重新构建实体框架,而大部分人在面对自己的旧版本软件时当然也会面临同样的难题。
EF7 并非为数据访问创建新的框架。而是构建一个新的、可持续性更高的库,该库不仅支持您多年来依赖于 EF 的功能和工作流,还支持很多新功能。团队还在争论该产品应该是下一代 EF 还是新的数据访问技术。我一度怀疑它是否应该是“EF 精简版”。但是,EF 的核心功能仍然存在,因此经过几番思考之后,我认为将其视为实体框架的下一个版本比较有意义。您可以在团队博文《EF7 – v1 或 v7?》(bit.ly/1EFEdRH) 中了解更多详细信息。
精简旧版,保留核心
对于一些开发人员而言,也有一些关于 EF7 的消息令人不安。虽然大部分常用的 EF 类、模式和工作流将被完整保留,但是一些较少使用的成员将被抛弃。但是,请勿惊慌;我将详细地讨论这一问题。
让开发人员继续使用熟悉的模式,甚至能够将大量的现有代码移植到 EF7 中是关键目标。您仍可以使用 DbContext、DbSet、LINQ 查询、SaveChanges 和早已成为 EF 一部分的很多交互方式。
我在 EF7 中定义的 DbContext 类如下:
public class BreweryContext : DbContext {
public DbSet<Brewery> Breweries { get; set; }
public DbSet<Beer> Beers { get; set; }
}
此外,以下是 EF7 中的一个简单更新,和 EF6 一样。我使用的是同步保存,但也提供所有异步方法:
public void StoreBeers(List<Beer> beers) {
using (var context = new BreweryContext()) {
context.Beers.AddRange(beers);
context.SaveChanges();
}
}
一个简单查询如下:
using (var context = new BreweryContext()) {
return context.Breweries.Where(b=>b.Location.Contains("Vermont"));
}
我使用的 EF7 版本位于包含版本 beta2-11616 的包中。尽管现在 EF7 实际上还未处于测试阶段,但是“beta2”与 NuGet 包命名决定有关。截至本文发布时为止,EF7 又得到了进一步完善。因此,请注意上述内容只是大概介绍,并非保证。
正如我一直所做的那样,我仍然拥有 DbContext,且定义了 DbSets。OnModelCreating 也仍然在,只是我没有在本例中使用。
EF4.1 中引入了 DbContext API,其更专注于 EF 的典型用法。实际上,它仍然依赖于原始 ObjectContext,因为 ObjectContext 可提供数据库交互、管理事务并跟踪对象状态。从那时起,DbContext 成为了使用的默认类,如果您希望与 ObjectContext 的交互较少,则可以下降到较低级别的 API。EF7 将抛弃冗余的 ObjectContext;只保留 DbContext。但部分依赖于 ObjectContext 的任务仍可访问。
一些难以支持且不常使用的极其复杂的映射将在 EF7 中消失。正如上述博文中所述:“例如,您可以拥有将 TPH、TPT 和 TPC 映射以及 Entity Splitting 组合在同一层次结构中的继承层次结构。”如果您曾经尝试直接使用 MetadataWorkspace API,并尖叫着跑开了,您将会发现它是一个错综复杂的怪物,可用于支持这种灵活性。但是这种复杂性将阻碍团队支持用户请求的其他方案。通过简化映射的可能性,MetadataWorkspace API 也变得更加简单和灵活。您可以轻松地从 EF7 中的 DbContext API 中获取有关模型架构的元数据,其可赋予您执行高级技术的低级功能,而无需处理低级的 ObjectContext。
放弃 EDMX,但继续实行数据库优先
实体框架当前使用两种方法来描述模型。一种使用设计器中的 EDMX;另一种涉及类,即代码优先 API 使用的 DbContext 和映射。如果您使用的是 EDMX 和设计器,则运行时 EF 会在 EDMX 后从 XML 中创建内存中模型。如果您选择代码优先路径,则 EF 会通过读取类(即您提供的 DbContext 和映射)来创建相同的内存中模型。从那以后 EF 的工作方式是相同的,无论您如何描述模型。请注意,通过 EDMX/设计器工作流,您还可以获取 POCO 类和 DbContext 供您在代码中使用。但是由于 EDMX 的存在,因此不会使用它们来创建该内存中模型。当您阅读下文时,理解很重要:EF7 将不支持基于设计器的 EDMX 模型。它无法在运行时读取 EDMX XML 来创建内存中模型。它将只使用代码优先工作流。
当团队发表有关这方面的博文时,引起了很多开发人员的恐慌。部分原因是很多开发人员尚未意识到可以将数据库反向工程到 POCO 类、DbContext 和映射。换言之,您可以从数据库开始来获取代码优先模型。可以通过 2011 年初首次发布的 EF Power Tools Beta 来实现这一目的。该工具受 EF6.1 设计器支持,且肯定也会受 EF7 支持。我已多次提到“代码优先”这一名称有一些迷惑性和误导性。最初它称为“仅限代码”,后来这一名称改为“代码优先”以完美匹配“数据库优先”和“模型优先”。
因此,要从现有数据库开始,您无需设计器或 EDMX。
但是,如果您拥有现有 EDMX 模型,并且不想失去使用设计器的能力,会怎样呢?有一些第三方设计器支持实体框架,例如早已支持 EF 代码优先的 LLBLGen Pro Designer (bit.ly/11OLlN2) 以及 Devart Entity Developer (bit.ly/1yHWbB2)。查找可能提供支持 EF7 的设计器的工具以及其他可能的软件。
还要记住另一个方法:坚持使用 EF6!
占用空间较少,适用的设备和操作系统较多
此外,Microsoft 还致力于简化 EF API 的分发。EF6.1.1 的 NuGet 包文件夹大约 22MB。包括 5.5MB 的 .NET Framework 4.5 程序集和其他内容(如果您使用 .NET Framework 4)。对于 EF7,有一些较小的 DLL。您可以仅组合支持工作流所需的 DLL。例如,如果针对 SQL Server,则使用核心 EntityFramework.dll、针对 SQL Server 的 DLL 以及包含关系数据存储共用的 API 的 DLL。如果要使用迁移,则您可以跳过该单独程序集。否则,您可能要从包管理器控制台创建并执行迁移。有一个适用于命令的 API。通过 NuGet 包管理器,可以通过包依赖关系识别并下载适当的包,因此您无需过于担心其详细信息。
其作用是尽量减少 EF7 在最终用户的计算机或设备上的占用空间,这对所有设备而言都非常重要。ASP.NET 也采用此方法。这两种技术都摆脱了对完整 .NET Framework 的依赖。相反,它们将只分发完成给定应用程序的任务所需的 DLL。这意味着 Windows Phone 和 Windows Store 应用商店应用使用的简化版本的 .NET 可以使用 EF7。
这还意味着,使用 Mono 而非完整 .NET Framework 的 OS X 和 Linux 等操作系统也可以支持客户端实体框架。
超越关系
第一次引入实体框架时,Microsoft 的愿景是将它应用于多种数据存储,尽管第一次是应用于关系数据库。和现在广受欢迎的 NoSQL 数据库(尤其是文档数据库)不同,当时也存在非关系数据库,但并未广泛使用。
尽管 EF 是对象关系映射 (ORM),但使用它的开发人员希望可以使用相同的架构与非关系数据库交互。EF7 将为此提供较高级别的支持,但是请记住较高级别的真正含义。关系数据库和非关系数据库之间有极大的差异,但 EF 并不会尝试掩盖这些差异。但对于基本查询和更新,您可以使用您已经熟悉的模式。
图 1 显示来自针对 Microsoft Azure Table Storage(一种非关系文档数据库)的示例应用的代码。该示例来自 EF 项目经理 Rowan Miller,您可以访问 github.com/rowanmiller/Demo-EF7,了解详细信息。请注意,该示例可针对 EF7 Alpha 每夜构建版本的 11514 版本运行。
图 1 为使用 Azure Table Storage 定义的 DbContext
public class WarrantyContext : DbContext
{
public DbSet<WarrantyInfo> Warranties { get; set; }
protected override void OnConfiguring(DbContextOptions options) {
var connection =
ConfigurationManager.ConnectionStrings["WarrantyConnection"]
.ConnectionString;
options.UseAzureTableStorage(connection);
}
protected override void OnModelCreating(ModelBuilder builder) {
builder.Entity<WarrantyInfo>()
.ForAzureTableStorage()
.PartitionAndRowKey(w => w.BikeModelNo, w => w.BikeSerialNo);
}
}
OnConfiguring 方法是新方法。该方法可影响 EF 如何在运行时配置 DbContext,类似于您现在可以对 DbConfiguration 类执行的操作。请注意 builder.UseAzureTableStorage 扩展方法,存在该方法是因为我还在项目中安装了 EntityFramework.AzureTableStorage 包。
EF7 为其不同的提供程序使用此模式。以下是针对 SQLite 的项目中 DbContext 类中的 OnConfiguring 方法:
protected override void OnConfiguring(DbContextOptions builder) {
string dir = ApplicationData.Current.LocalFolder.Path;
string connection = "Filename=" + Path.Combine(dir, "VermontBrewery.db");
builder.UseSQLite(connection);
}
此项目已安装了 EntityFramework.SQLite 包,因此现在我改为拥有 UseSQLite 扩展方法。
回到图 1 中的 WarrantyContext 类,您可以看到熟悉的 OnModelCreating 替代了 DbContext,并且我在其中进行了一些特殊的映射。同样,我拥有 EntityFramework.AzureTableStorage NuGet 包提供的方法。我要基于我需要的功能选择包。Azure Table Storage 依赖于键值对进行唯一标识以及支持表分区。为了检索或存储数据,知道为 PartitionKey 和 RowKey 使用什么值很关键,因此 API 提供一个方法 PartitionAndRowKey,该方法可以用于将属性映射到适当的键。这一概念无异于您可以如何使用 Fluent API 或数据注释来指定映射到关系数据库主键的属性。
由于此映射功能,我可以编写熟悉的 LINQ 查询来检索一些数据:
var warranty = _context.Warranties
.Where(w =>
w.BikeModelNo == modelNo
&& w.BikeSerialNo == serialNo)
.SingleOrDefault();
因此您看到的是一个典型的 LINQ 查询,但它是针对 Azure Table Storage 数据存储执行的,就像您现在可以对关系数据库执行的操作一样。
这一相同演示还可以更新保证对象;使用 DbSet.Add 创建和插入新的对象;使用 DbContext.SaveChanges 将所有内容保留到数据存储,就像现在使用 EF6 执行的操作以及在整个 EF 历史中执行过的操作一样。
此外,要考虑的比较有趣的一点是,实体框架如何始终支持映射到关系数据库的规范功能集,但由数据库提供程序指定这些功能集如何转换为其针对的数据库。EF7 将包含可被关系数据存储和非关系数据存储理解的高级规范功能集。还有专注于关系数据库的低级功能集,其密封在 EntityFramework.Relational 程序集中。所有关系数据库提供程序将依赖于这些功能,并且像现在一样,它们对数据库交互的特定处理将在其自己的提供程序 API(例如我之前使用的 EntityFramework.SQLite)中进行。您将在衍生 AsRelational 方法(位于关系 API 中)的提供程序中找到扩展方法。它是 DbContext 的扩展方法。
甚至还有一个内存中数据存储提供程序,可用于在您想要避免数据库交互(在您测试的逻辑中可能涉及到)时进行单元测试。在这些情况中,您通常可以使用虚设或模拟框架来模拟数据库交互。
如果您要设置一个测试来执行查询或更新数据库,则最好使用一些代码来实例化数据库,例如:
using (var context = new BreweryContext()) {
// Perform some action against the context
}
通过先向测试项目中安装 entityframework.InMemory 包,为 InMemoryStore 定义 DbContextOption,然后指定上下文应使用该选项,您可以轻松地切换到内存中存储。同样,这可能是因为此 API 提供了扩展方法:
var options = new DbContextOptions().UseInMemoryStore();
using (var context = new BreweryContext(options)){
// Perform some action against the context
}
功能更多,灵活性更高
您可以在扩展方法提供的灵活性以及通过 OnConfiguring 重载影响实体框架管道的能力方面看到新代码库的优势。在整个新代码库中有扩展点,这些扩展点不仅用于更改 EF7,还可以让您更容易地将自己的逻辑插入到 EF7。
通过新的核心代码库,EF 团队可以解决一些老问题。例如,我使用的版本已支持批量更新,这是关系数据库的默认设置。我已使用允许我在 LINQ 查询中内嵌使用我自己的方法的代码,而不会收到令人讨厌的“实体框架无法将此方法转化为 SQL”的消息。相反,EF 和提供程序可以解析查询的哪个部分将变成 SQL,哪个部分将在客户端本地运行。我相信肯定会有相应的保护和指导,以避免有关该特定功能的一些潜在的性能问题。
团队可以为模型添加长久请求的唯一外键功能。他们也在仔细研究如何提供对表值函数的支持以及处理断开连接数据的更干净的方法,这是我多年以来一直关注实体框架的一点。这是断开连接应用程序的常见问题,并非仅当涉及到实体框架时才有,并且很难创建在每种方案中都可以一致运行的算法。因此,确实需要一种新方法。
EF7 有很多令人兴奋的新功能。我强烈建议您仔细阅读 ADO.NET 团队博客 (blogs.msdn.com/adonet) 上的博文。除了我之前链接的博文,Rowan Miller 还深度讨论了有关在 EF7 中放弃支持设计器的决定;请参阅《EF7 -“代码优先”的实际意义》(bit.ly/1sLM3Ur)。请关注该博客以及 GitHub 项目。GitHub (bit.ly/1viwqXu) 上的 wiki 包含指向如何访问每夜构建版本、如何下载、编译和调试源代码以及一些演练和设计会议记录的链接。团队希望得到您的反馈,对能够收到 Pull 请求感到非常高兴。
谨慎的决定
对我而言,撰写有关 EF7 的文章很重要,因为这有助于缓解有关这一巨大变革以及 EF7 中不再提供对您的应用程序来说不可或缺的一部分现有 EF 功能所带来的恐惧心理。这些恐惧并非空穴来风,而团队和我都会谨慎对待。但是 EF6 并不会离我们而去,而是将在社区的帮助之下继续完善,了解这一点很重要。如果您要利用这一发展,则要作出一些艰难的选择。升级大型应用程序并非易事,您必须仔细权衡相关选择。或许您可以分解应用程序,仅重新编写其中一部分以便从 EF7 中受益。
同样,当我撰写此专栏时,EF7 仍处于其早期阶段,我不确定当您阅读本文时 EF7 进展如何。但您可以就当前可用的源代码和 NuGet 包进行探索、实验并提供反馈。请记住,团队在完善核心 API 时可能不会一直保持所有提供程序 API(例如 Redis、SQLite 等)最新。根据 bit.ly/1ykagF0 上的博文《EF7 - 优先级、关注和首次发布》,首版 EF7 将关注与 ASP.NET 5 的兼容性。后续版本将添加更多功能。尽管 EF7 尚未足够稳定到可以用来构建应用程序,但是足以让您开始提前进行规划。