随着 Entity Framework 最新主版本 EF6 的推出,Microsoft 对象关系映射 (ORM) 工具达到了新的专业高度,与久负盛名的 .NET ORM 工具相比已不再是门外汉。 EF 已经完全成熟,正在超越以前广泛使用的工具。
Entity Framework 已经度过了青涩期,它最初只是供数据库开发者使用的工具,后来在 .NET 社区的敏捷开发者中间引起轰动。 它学会了如何摆脱应用程序开发模式,转向了普通旧 CLR 对象 (POCO) 模型,支持以测试和域为中心的软件开发,同时没有剥夺以数据为中心的开发者的使用权利。 一路走来,它解决了生成代码的性能问题和无数与质量有关的问题,并赢得了众多数据库管理员 (DBA) 的青睐。
从 EF 4.1 开始,Microsoft 认识到了 EF 所需的复杂性,通过推出 DbContext API 简化了对其功能的访问。同时,由于不是所有人都想使用设计器或生成代码,它可以让您利用自己的代码生成模型。 在此期间,发生了另一项重大变化,但不是功能、语法、代码或性能的变化。 EF 团队变得更加透明,与用户社区的互动更加频繁,它开始更加流畅地提供功能发布,而不是将它们与 Microsoft .NET Framework 捆绑在一起。 自 2012 年 EF5 发布后,这种做法带来了两个方面的进步。 首先,从 .NET Framework 中提取所有的 Entity Framework API,并与团队同时正在开发的非常规功能 API 组合在一起。 其次,整个开发工作改用了开源模型。 EF6 在以下网站中公开开发:entityframework.codeplex.com。 您不仅能通过会议记录、签入和可下载夜间生成了解团队所做的工作,还可以向 EF6 提供源代码(但是要在 EF 团队的完全监督之下)。
请记住 EF6 是演变而不是革命。 几乎您原先掌握的所有 EF 技能都没有变化,例如如何生成 Entity Framework 模型以及如何在您的应用程序中使用 EF。 尽管 EF6 是在 ORM 基础上发展而来的,但是并没有改变它根本的工作方式。 如果您已经投入时间学习 EF,那么这种投入将不会白费。 EF6 在某些方面变化还是比较大的,但是这些变化仅限于部分命名空间的变化,如果您有准备的话会很容易处理。 我会在本文最后告诉您一些有帮助的资源。
我认为 EF6 的功能分为以下几类:
- 免费提供的功能:这些功能属于核心功能的一部分。 您甚至无需知道它们有什么作用,更不必说需要知道有什么新的代码了。 该组包括的功能有通过重写视图生成引擎和查询编译修改来提高性能,由于 DbContext 能使用打开的连接而获得的稳定性,以及 Entity Framework 创建的 SQL Server 数据库的更改设置。
- 级别设置功能:改进较大之处是 Code First 现在支持映射存储过程,而在设计器中创建的模型已支持此功能。 第 9 频道视频对此功能已进行了颇多介绍(例如位于以下网址的视频:bit.ly/16wL8fz),而且 CodePlex 网站提供了详细的规范介绍,所以我在本文中不再重复介绍这方面的信息。
- 另外一处更改更为有趣。 正如我刚才提到的,EF6 的 EF API 是从 .NET Framework 中提取的;它们现已完全封装在 NuGet 程序包中。 这意味着 EF5 采用的部分功能(例如枚举、空间数据支持和性能改进)不再依赖于 .NET 4.5。 所以,如果您的 EF6 使用的是 .NET 4,那么这些功能最终会给您带来帮助。
- 我也将 EF 设计器归入了这一类。 从 2013 版开始,Visual Studio 已取消此功能,但是作为 Visual Studio 的扩展功能提供。 对于 EF6 而言,将设计器作为扩展功能具有相当大的好处。 以后团队将能够直接向设计器添加功能,包括 Entity Framework Power Tools 中当前提供的功能。 通过使设计器与 Visual Studio 分离,可以使 Microsoft 为 Visual Studio 2012 和 Visual Studio 2013 提供 EF6 工具。
- 专家功能:这些功能是基本 EF 应用程序示例所不具有的、您渴望拥有的功能。 EF6 中有许多这样的功能:支持异步查询和保存、返回自定义 Code First 约定、利用新的 DbConfiguration 类型提高可扩展性(依赖于较低级别的 EF6 IDbDependency 解析程序)、支持单元测试模拟、可配置不稳定连接的重试次数等。 您无需成为认证专家就能使用这些功能,但您在使用时肯定会感觉像专家。
我还想重点介绍一个特殊类别:由社区成员贡献的 EF6 代码。 Unai Zorrilla 添加了 DbSet.AddRange 和 RemoveRange,利用它们可以自定义复数化和方便的 DbChangeTracker.HasChanges 方法。 他还在开发供 EF 将来迭代使用的其他很酷的功能。 Erik Jensen 是一位 SQL Server Compact (SQLCE) MVP,他贡献的 SQLCeFunctions 与 LINQ to Entities 查询使用的 SQL Server 函数 SqlFunctions 非常相似。 EF 视图生成速度的大幅提高(对于大型复杂模型最为显著)要归功于 Alireza Haghshenas 和一位名为 VSavenkov 的 CodePlex 成员。 在 Iñaki Elcoro(即 CodePlex 上的 iceclow)的帮助下,现在还可以定义自定义迁移操作。 (EF 团队的 Rowan Miller 撰写了一些关于此功能的博客文章;第一篇位于以下网址:bit.ly/ZBU0w1。)全部参与者的名单可以在团队博客文章《可用的 EF6 RTM》中找到,网址是:bit.ly/1gmDE6D。
我将在本文中深入探讨部分公开度不高的主题,并告诉您详细介绍其他主题的现有资源。
MSDN 数据开发者中心的版本历史记录页面 (bit.ly/1gCT0nz) 列出了所有功能,每项功能提供一两句详细信息,部分提供指向更多信息的链接。
一切迎刃而解:性能改进与稳定性
性能是许多软件项目的软肋,Entity Framework 从一开始就受到了许多性能方面的批评。 但是,EF 的每一次迭代都给这个方面带来了巨大的改进。
对性能影响最大的因素之一是首次使用应用程序进程中上下文时的启动时间。 但是,我们可以通过多种方法改善启动时间。 希望您已经从我的文章或其他资源中了解这些方法,例如以下网址提供的关于性能问题的 MSDN 文章:bit.ly/3D6AiC。
通常影响性能的启动步骤是映射视图的生成,EF 会利用此过程创建相关的 SQL,用来查询模型中的每个实体集。 当应用程序运行时,会利用这些视图,因此对于部分查询而言,EF 不必动态创建 SQL。 无论使用 EF 设计器还是使用 Code First 创建模型,都会进行视图生成。 为了节省时间,可以预先生成这些视图,再将其编译到应用程序中。
对于大型复杂的模型而言,视图生成尤其耗时。 EF6 对此过程进行了改进,使得无论预先生成视图还是在运行时生成视图,都能显著提高速度。 请注意,EF 6.0.0 版本的一项缺陷妨碍了此功能,但是该缺陷在 EF 6.0.1(于同一天发布)中得到了纠正,(在编写时)采用通过 NuGet 获得的默认程序包。 此外,由于对运行时 EF 使用这些生成视图的方式进行了改进,因而改善了查询执行时间。 小模型或简易模型的视图生成过去从来不是问题。 但是,许多企业的模型都有数百个实体,同样包括继承、关系和其他复杂问题。 此更改将使这些企业大为受益。
关于如何使用 Entity Framework 程序集中的 Ngen,请参阅 EF6 发布的公告博客文章中的另一篇性能笔记,网址是:bit.ly/1gmDE6D。
LINQ Contains 的编译速度更快 EF 团队不断地调整查询创建方式,该团队重点进行的一项更改是如何对使用 LINQ Contains 的查询进行编辑。 说得更清楚些,就是提高了编译进程的性能。 由于生成的 SQL 没有变化,因此数据库的查询执行不受影响。
SQL Server 数据库创建 EF6 其中一项稳定性的改进与数据库创建有关。 Model First 和 Code First 工作流都能为您创建数据库。 如果该数据库采用 SQL Server,EF 现在针对 SQL Server 数据库采用了一项“最佳实践”,即将数据库的 READ_COMMITTED_SNAPSHOT 设置配置为 ON。 这意味着在默认情况下,数据库会在每次更改时都为自己创建一个快照。 在实际数据库中执行更新时,会对该快照执行查询。 我在最近一篇博客文章“What’s that Read_Committed_Snapshot Transaction Support for EF6 About Anyway?”(Read_Committed_Snapshot 事务究竟支持 EF6 的哪一点?)中对此功能进行了介绍,网址是:bit.ly/14FDpZI。
重用打开连接 最后,消除了一项令人心烦的限制:EF6 可让您对打开的 DbConnection 执行上下文调用。 在过去,如果您显式打开一个连接后执行使用该连接的 EF 命令,或者,如果您试图重用已被另一个上下文调用打开的连接,将会引发异常并显示如下消息,“实体连接只能用关闭的 DbConnection 构造。”现在,EF6 会非常乐意让您重用已经打开的连接。
专家增强功能
异步支持 在我 2013 年 3 月的数据点专栏文章“Playing with the EF6 Alpha”(小试 EF6 Alpha)中,我对几项功能进行了探讨(异步查询、SaveChanges 和自定义约定)(msdn.microsoft.com/magazine/jj991973)。
异步支持将 .NET 4.5 Await 和 Async 模式引入了 EF 的 LINQ 查询执行方法,增加了 FirstAsync、 FirstOrDefaultAsync、SingleAsync、SingleOrDefaultAsync、ToListAsync、ForEachAsync 等。 如需查看完整列表,请打开 System.Data.Entity.QueryableExtensions。 DbSet 获得了 FindAsync,而 DbContext 获得了 SaveChangesAsync。 自从该文章发表后,没有发生太多变化,您不妨看一下该文章了解详细情况。 此外,Microsoft 还创建了部分演练和有趣的详细规范,您可以从我之前提到的版本历史页面获取这些信息。
自定义代码约定 在该文章中,我还介绍了自定义 Code First 约定,当然也是另一项专家功能。 在 Code First 最初版本中 EF 团队对此进行了开发,但是由于发布受阻,该团队被迫对其进行搁置,使得许多开发者非常失望。
假定您要将一个公共映射作为一般规则映射到您的实体或属性。 现在,您可以将其定义为一个约定,无需单独为模型每个实体或属性指定映射,而且它会被全面应用到所有实体上。 例如,如果您要用 50 个字符表示数据库中的每个字符串属性,无论您的数据库提供程序使用何种默认值,您都可以将此规则指定为约定。 由于约定采用 Code First Fluent API,因此如果您这样配置映射的话,生成约定应该会感觉非常熟悉。
modelBuilder.Properties<String>().Configure(p => p.HasMaxLength(50))
现在,此模型的每一个字符串将映射到一个 50 字符的数据库列。 与 Fluent 或注释配置一样,您可以指定属性或实体的约定,并控制继承映射。 通过约定来影响关系是基于模型的约定要处理的一个不太常见且更为复杂的任务。 有关详细信息,请访问 bit.ly/1gAqcMq。 您还可使用约定作为数据注释。 当 Code First 生成模型时可以使用一个用来执行约定的层次结构。 默认情况下,内置约定先运行,自定义约定后运行。 但是,您可以强制使自定义约定在内置约定前执行。 有关示例请参阅“Custom Code First Conventions”(自定义 Code First 约定),网址:bit.ly/14dg0CP。
连接复原 如果在 EF 尝试执行查询或保存更改时连接断开,您现在可以让 EF 重试。 尽管断开连接是企业 Intranet 的问题,但是经证明连接复原对于帮助应用程序与云连接非常有用。 使用 IDbConnectionStrategy 可以配置重试次数。 EF 中包含的 SQL Server 提供程序用来指定 default:SqlServerExecutionStrategy,它会显示错误消息告知调整瞬态连接引发异常的策略。 另外一个策略 SqlAzureExecutionStrategy 通过微调可以连接 Windows Azure SQL 数据库。
最简单的策略指定方法是使用新的 DbConfiguration 类,使用该类可以很容易配置特定数据库提供程序的行为。 以下命令可以让 EF 针对 SqlClient 使用 SQLAzureExecutionStrategy:
SetExecutionStrategy (SqlProviderServices.ProviderInvariantName, () => new SqlAzureExecutionStrategy());
不仅连接策略可以配置,而且您还可以自己创建策略并根据需要通过编程暂停使用它们。 EF 团队成员 Miller 在他的博客文章里介绍了如何暂停使用,网址是:bit.ly/14gPM1y。
我利用另一位 EF 团队成员 Glenn Condron 建议的方法对 SqlAzureExecutionStrategy 进行了测试。 为了触发此策略寻找的特定瞬态连接故障错误代码,我利用新的 EF6 命令拦截功能引发了一个瞬态连接错误。 然后我运行了测试,测试结果显示,当我设置执行策略时,在初次失败后查询重试了五次。 一位开发者对我在博客文章中介绍的此功能有一段非常棒的评论,他说他的公司已经感受到了此功能带来的好处 (bit.ly/HaqMA0)。
还有一个非常有趣的算法,可以确保不同线程的重试不会全部同时执行。
共享 DbTransactions 和 DbConnections 我希望到现在您已明白,默认情况下 EF 始终使用 DbTransaction 对数据库进行调用。 例如,当调用 SaveChanges 时,首先创建一个 DbTransaction,然后再将第一个命令发送到数据库。 EF 随后向数据库发送所有必要的插入、更新和删除命令,并最终提交事务。如果一个命令失败,所有以前执行的命令将进行回滚。
通过启动 TransactionScope 来包装 EF 调用和任何其他需要包含在相同事务中的调用(未必是有关数据库或 EF 的调用),您始终可以替代默认行为。 现在,通过 EF6 的附加功能,可以让单个 DbTransaction 负责多个数据库调用。 请注意,如果您要在事务或分布式事务中增加非数据库逻辑来调用不同的数据库,您仍然需要使用 TransactionScope。
与 EF6 共享 DbTransactions 的关键是利用新的 BeginTransaction 方法返回对当前 DbTransaction 的引用或利用 UseTransaction 方法。
以下代码所展示的是默认行为:
//code to create two new casinos, "casino1" & "casino2" var context = new CasinoSlotsModel(); context.Casinos.AddRange(new[] { casino1, casino2 }); context.SaveChanges(); context.Database.ExecuteSqlCommand ("Update Casino.Casinos set rating= " + (int) casino.Rating)
我的探查器显示了每个上下文调用所使用的事务,其中一个调用 SaveChanges,用来触发两个插入,一个调用 ExecuteSqlCommand,用来触发更新,如图 1 所示。
图 1 包装于自身事务中的独立上下文调用命令
现在我要修改共享事务的代码,以此包装 SaveChanges 和 ExecuteSqlCommand 调用。 我要使用新增的 DbContext.Database.BeginTransaction 对 System.Data.Entity.DbContextTransaction 进行显式实例化处理,需要的话还会打开一个连接(请记住,尽管 DbContext.Database.Connection 的命令相似,但却返回 System.Data.Common.DbTransaction,该事务无法被 EF 命令共享):
using (var tx = context.Database.BeginTransaction()) { try { context.SaveChanges(); context.Database.ExecuteSqlCommand ("Update Casino.Casinos set rating= " + (int) casino.Rating); tx.Commit(); } catch (Exception) { tx.Rollback(); } }
从图 2 中可以看到同一事务中包装的所有命令。
图 2 单个事务中所有上下文调用的命令
还有一项名为 UseTransaction 的新功能。 您可以启动 DbTransaction,然后用它来执行 ADO.NET 调用,然后利用 DbContext.Database.UseTransaction 甚至可以从同一事务中的单独上下文实例来执行 EF 调用。此示例位于以下网址:bit.ly/1aEMIuX。
由于 EF 现在可以利用已打开的连接创建 EntityConnection(由 ObjectContext 在后台执行),还可以显式重用打开的连接。 同时,上下文不会关闭不是自己打开的连接。 我写了一个简单的测试程序,通过它我在执行上下文调用前打开了一个上下文连接。
[TestMethod] public void ContextCanCreateEntityConnectionWithOpenConnection() { using (var context = new CasinoSlotsModel()) { context.Database.Connection.Open(); Assert.IsNotNull(context.Casinos.ToList()); } }
执行 ToList 调用时,DbContext 会创建一个 ObjectContext 实例,后者进而创建一个 EntityConnection。然后,它利用 EntityConnection 打开 DbConnection,并在调用完成返回所有结果后关闭该 DbConnection。 在 EF5 中运行此程序时会导致发生异常(“EntityConnection 只能使用关闭的 DbConnection 进行构造”),但是由于行为发生了变化,它在 EF6 中却能成功运行。 如果您要进一步控制连接状态,此更改可以让您重用连接。 规范建议在以下情况下使用,例如“在不能保证连接的状态时,共享组件之间的连接。”
AddRange 和 RemoveRange 如前所述,AddRange 和 RemoveRange 由社区成员 Zorrilla 贡献。 每个方法将可枚举的单个实体类型作为自己的参数。 在共享 DbTransactions 部分的第一个代码示例中,我在传入 Casino 实例数组时使用了 AddRange。
context.Casinos.AddRange(new[] { casino1, casino2 });
由于默认情况下 Entity Framework 在每个添加和删除方法中调用 DetectChanges,因此这些方法比一次添加或删除单个对象时执行速度快很多。 如果使用 Range 方法,可以在只调用一次 DetectChanges 的同时处理多个对象,从而显著提高了性能。 我用 5、50、500、5,000 甚至 50,000 个对象对此进行了测试,至少在我的情形下数组大小没有限制,速度确实非常之快! 请记住,此改进只有在使上下文作用于对象时有效,对于 SaveChanges 则无效。 调用 SaveChanges 仍然一次只执行一个数据库命令。 因此,当您快速将 50,000 个对象添加到上下文中时,在调用 SaveChanges 时仍然要单独执行 50,000 个插入命令,恐怕您在实际系统中是不会这样做的。
另一个方面,对于在不需要 EF 跟踪对象的情况下对批量操作的实现支持 (bit.ly/16tMHw4) 及通过一次数据库调用同时发送多个命令的批量操作的讨论由来已久 (bit.ly/PegT17)。 尽管这两种功能在初始 EF6 版本中都没有采用,但是两者都非常重要,并计划在将来版本中采用。
与您的编码风格冲突较少 在 .NET 中,可以通过重写 System.Object.Equals 方法来定义系统的等同性规则。但是,Entity Framework 有自己确定被跟踪实体等同性的方法,而且此方法依赖于标识。 如果您已经重写了 Equals(及 Equals 所依赖的 GetHashCode 方法),您的 Entity Framework 更改行为跟踪可能会出现问题。 Petar Paar 在他的博客文章中非常清楚地展示了此问题,网址:bit.ly/GJcohQ。 为了纠正这个问题,EF6 现在使用自己的 Equals 和 GetHashCode 逻辑来执行更改跟踪任务,而忽略您可能已编写的任何自定义 Equals 和 GetHashCode 逻辑。 但是,您仍然可以在自己的域逻辑中显式调用您自己的自定义方法。 通过这种方法,这两种方式就能和谐共处。
如果您非常重视图形和聚合,可能需要在其他类型中嵌套类型。 但是,Code First 模型生成器无法发现嵌套类型,进而无法在模型中创建实体或复杂类型。 图 3 显示了一个嵌套类型示例:Address。 由于我只会在 Casino 类型中使用 Address,因此我将它嵌套在了 Casino 类中。 由于 Address 本身不需要标识,因此我还将 Address 创建为域驱动设计 (DDD) 值对象。 (请参阅我 2013 年 10 月的数据点专栏,“Coding for Domain-Driven Design:Tips for Data-Focused Devs, Part 3”(域驱动设计的编码:以数据为中心开发的提示,第 3 部分)详细了解 DDD 值对象,网址:msdn.microsoft.com/magazine/dn451438。
图 3 我的含有嵌套 Address 类型的 Casino 类
public class Casino() { //...other Casino properties & logic public Address PhysicalAddress { get; set; } public Address MailingAddress { get; set; } public class Address:ValueObject<Address> { protected Address(){ } public Address(string streetOrPoBox, string city, string state,string postalCode) { City = city; State = state; PostalCode = postalCode; StreetOrPoBox = streetOrPoBox; } public string StreetOrPoBox { get; private set; } public string City { get; private set; } public string State { get; private set; } public string PostalCode { get; private set; } } }
我使用 EF Power Tools 获得了模型的可视化表示,而且在图 4 中我展示了使用 EF5 和 EF6 时分别得到的 Casino 实体。 EF5 没有识别出嵌套的类型,并且在模型中未包含 Address 或依赖属性(PhysicalAddress 和 MailingAddress)。 但是,EF6 能够检测到嵌套的类型,可以看出模型中表示的 Address 字段。
图 4 与 EF5 不同,EF6 能够看到嵌套的 Address 类型并包含依赖属性。
支持嵌套类型的相同内部更改还解决了另一个问题。 此问题因同一项目中不同命名空间下名称相同的多个类型而引起。 以前,当 EF 从 EDMX 中读取元数据并在程序集中寻找匹配的类型时,它不会注意命名空间。
通常,此问题造成的后果是在与包含实体的模型相同的项目中包含非 EF 表示的实体。 所以我的模型中可能包含代码生成的如下 PokerTable 类:
namespace CasinoEntities { public partial class PokerTable { public int Id { get; set; } public string Description { get; set; } public string SerialNo { get; set; } } }
还可能包含不在我的模型中但在同一项目中的 DTO 类:
namespace Casino.DataTransferObjects { public class PokerTable { public int Id { get; set; } public string Description { get; set; } public string SerialNo { get; set; } } }
如果项目以 EF5 为目标,当生成项目时会看到以下错误:
CLR 类型到 EDM 类型的映射不明确,因为多个 CLR 类型与 EDM 类型“PokerTable”匹配。以前找到的是 CLR 类型“MyDtos.PokerTable”,新找到的则是 CLR 类型“EDMXModel.DTOs.PokerTable”。
在使用 EDMX 设计时会看到此错误。 如果使用 Code First,在遇到相同情形时 — 两个匹配的类名称相同但命名空间不同,并且其中一个类位于模型中 — 当模型生成器开始解释 Code First 模型时,您会在运行时发现此问题。
长期以来,此问题一直令人困扰。 我读完该消息后问我的计算机:“您看到不同的命名空间了吗? 在吗?”我还收到了很多经历过此问题的朋友、客户和其他开发者发来的电子邮件。
EF6 现在能够识别命名空间并允许这种情形存在。 关于引起这两点变化的内部机制的详细信息,请阅读 Arthur Vickers 发表的博客文章,网址:bit.ly/Wi1rZA。
将上下文配置移到代码中 在您的 app.config 或 web.config 文件中有许多设置可用来定义上下文的使用方式。 例如,您可以指定数据库初始化、迁移或默认数据库提供程序。 我采用的添加这些设置的方法通常是复制和粘贴,因为这些设置太难记,牵涉到许多字符串。 现在有了 EF6,您可以使用 DbConfiguration 类在代码中声明许多上下文配置。 在三月的数据点专栏中,我对此进行了试用,结果遇到了争用条件造成的缺陷,我对此进行了报告并进行了修复。 现在我再来谈一下 DbConfigurations(又称为基于代码的配置)。 请注意,在谈到 Code First 配置映射、DbConfiguration 设置及用于定义更改模型时数据库迁移方法的 DbMigrationConfigurations 时,会有很多令人困惑的地方。 DbConfiguration 以 DbContext 设置为目标。
DbConfiguration 依靠 EF6 的另一超级专家、较低级别的功能:支持依赖关系解析,与 ASP.NET MVC 和 Web API 中使用的 IDependencyResolver 相似。 依赖关系解析使您可以使用代码中的服务定位器模式与控制反转 (IoC) 模式,使 EF6 能够从实现公共接口的分层可用对象中进行选择。 本例中的根接口是 IDbDependencyResolver。 尽管 EF6 包含许多 DbConfigurations 可供其发现和在上下文设置中放置优先顺序,但也可以利用依赖关系解析向 EF 中添加新功能。 我会继续介绍部分配置,我会在 bit.ly/QKtvCr 上为您介绍功能规范,使您进一步了解 IDbDependencyResolver。
您可以使用 DbConfiguration 指定熟悉的 EF6 上下文规则及新设置。 图 5 所示的示例配置类包含 EF 用来取代其默认行为的一系列不同的设置。 可以注意到,设置放在了类构造函数中。
图 5 示例配置类
public class CustomDbConfiguration : DbConfiguration { public CustomDbConfiguration() { SetDefaultConnectionFactory(new LocalDbConnectionFactory("v11.0")); SetDatabaseInitializer (new MigrateDatabaseToLatestVersion<CasinoSlotsModel, Configuration>()); //SetDatabaseInitializer(new MyInitializer()); SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy()); AddInterceptor(new NLogEfCommandInterceptor()); SetPluralizationService(new CustomPluralizationService()); } }
SetDefaultConnectionFactory 替换了配置文件 entityframework 部分中可能已经使用的 DefaultConnectionFactory 标记。 SetDatabaseInitializer 取代了在配置文件中或在应用程序启动时指定初始值设定项或迁移配置的操作。 我提供了两个示例,但其中一个已注释掉。 一旦在 EF 查询或其他执行命令期间发生连接中断,SetExecutionStrategy 可以用来指定处理办法。 SetPluralizationService 公开了 EF6 另一项新功能:即创建自定义复数化的能力。 稍后我会对此进行详细说明。
有许多其他方法会影响这些内置依赖关系解析程序的上下文。 MSDN 文档“IDbDependencyResolver Services”(IDbDependencyResolver 服务)(bit.ly/13Aojso) 列出了 DbConfiguration 提供的所有解析程序。 当提供程序编写人员需要注入上下文与提供程序交互的规则和逻辑时,依赖关系解析还可用于帮助他们解决部分问题。
查询和命令拦截 我刚才没有提到 CustomDbConfiguration 对 AddInterceptor 的使用。 DbConfiguration 在 IDbDependencyResolvers 中能做的不止是推送。 EF6 的另一项新功能是能够拦截查询和命令。 在拦截查询和命令的同时,您可以访问即将发送至数据库的生成 SQL 及这些命令返回的结果。 使用此信息可以记录 SQL 命令甚至修改它们,并告诉 EF 使用更新的命令。 尽管我非常喜欢使用此功能,我还是想留出些空间,建议您阅读 Arthur Vickers 介绍此功能的三章博客系列中的第 3 章:“EF6 SQL Logging – Part 3:Interception building blocks”(EF6 SQL 日志记录 — 第 3 章:拦截构建基块)(bit.ly/19om5du),其中有指向第 1 章(“Simple Logging”(简单日志记录))和第 2 章(“Changing the content/formatting”(更改内容/格式))的链接。
自定义 EF 复数化 在讨论自定义 EF 复数化的功能之前,我要确切地知道您是否了解其默认行为。 EF 利用内部复数化服务处理三项任务:
- 在 Database First 中,它要确保实体采用单数名称。 因此,如果数据库表的名称是 People,在模型中它会变成 Person。
- 在 Database 或 Model First 的 EF 设计器中,它会根据实体名称创建复数 EntitySet 名称(DbSets 的基础)。 例如,服务会确保 Person 实体的 EntitySet 名称是 People。 如果您使用 Database First 创建模型的话,它不会简单地使用表名称。
- 由于在 Code First 中 DbSets 采用显式命名,EF 会利用该服务推导出表名称。 如果开始时使用的类名称为 Person,约定会认为数据库表的名称是 People。
该服务的作用不是作为拼写字典(可以提供自定义拼写的文本列表)。 它使用的是内部规则。 除了偶尔出现异常外(最初我非常喜欢 Rhinoceros 这个实体名称),该服务最大的问题是规则采用英语。
Zorrilla 为 EF6 创建了 IPluralizationService 接口以便您添加自己的逻辑。 创建自定义服务后,可以按先前所示使用 DbConfiguration 将其插入。
目前,此自定义只能使用前面列表中的第三种情形:Code First 推导表名称。 从最初的 EF6 版本开始,该自定义不能应用到设计器上。
有两种使用该服务的方式。 开始先使用基本服务 — EF 中的 EnglishPluralizationService 或由其他人已经生成的服务 — 然后重写 Singularize 或 Pluralize 方法,添加您自己的规则。 此外,还可以在 CustomPluralizationEntry 类中指定一对单词,然后将该类附加到现有服务。 Zorrilla 在他的博客文章中介绍了 CustomPluralizationEntry (bit.ly/161JrD6)。
请留意未来的数据点专栏,我会在该专栏中介绍添加复数化规则的示例(不仅是单词对),并展示对于 Code First 数据库映射的影响。
Code First 的更多好处
有几项针对 Code First 的新功能我还没有介绍,特别是 Code First 迁移。 由于空间的关系,在此我先对它们进行概括介绍,再在 2014 年 1 月的数据点专栏中深入介绍它们:
- 能够创建迁移脚本,用来检查哪些服务已经运行,以便能够从任何迁移点修复数据库。
- 提高对 Migrations_History 表的控制,以适应不同的数据库提供程序。
- 能够指定数据库映射的默认架构,无需始终默认为 dbo。
- 能够通过迁移处理针对同一数据库的不同 DbContext。
- ModelBuilder 能够一次性添加多个 EntityTypeConfiguration,无需每个都使用一个代码行。
尽快使用专家!
在我看来,EF6 最需要铭记的一点是在 EF 现有功能的基础上增加了许多很好的功能。 如果您要将项目从 EF5 迁移到 EF6,需要注意部分命名空间的变化。 关于此迁移方法,团队在以下网址提供了详细的说明:bit.ly/17eCB4U。 除此之外,对于使用 EF6 应该充满信心,它是通过 NuGet 分发的 Entity Framework 的最新稳定版本。 即使您不想马上使用任何一项专家功能,请记住您仍会从本文前面介绍的性能提高中获得益处。 但我对于专家功能最感兴奋,非常感谢将这些功能也添加到 EF6 中的社区开发者。
EF 将继续进行演变。 尽管 EF6 第一版的发布时间与 Visual Studio 2013 的发布时间恰好一致,但是 EF 6.1 和更高版本已经处于开发阶段。 您可以在 CodePlex 网站上关注它们的进度。
Julie Lerman 是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 Microsoft .NET 主题的演示。她是《Programming Entity Framework》(2010) 以及“代码优先”版 (2011) 和 DbContext 版 (2012)(均出自 O’Reilly Media)的作者,博客网址为thedatafarm.com/blog。通过她的 Twitter(网址为 twitter.com/julielerman)关注她,并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。
衷心感谢以下技术专家对本文的审阅:Rowan Miller (Microsoft)
链接:https://msdn.microsoft.com/zh-cn/magazine/dn532202.aspx