数据点

Julie Lerman

Julie Lerman我使用了多个客户端帮助重构使用实体框架 (EF) 的现有软件。对于 EF 代码,有多种方式可以进行重构,且通常包括更新。在此专栏中,我们来看看您可以修改 EF 代码或包含 EF 的应用程序的几种方法。针对每一种方法,我将根据客户端生产应用程序的使用经验提供一些指导,有助于您做好准备,避免一些麻烦。

我要讨论的更改包括:

  • 更新到新版本的实体框架
  • 分解大型实体数据模型
  • 将 ObjectContext API 替换为 DbContext API

本月我会在较高层次的基础上介绍前两项更改,然后在我的下一篇专栏中按照指导深入探讨最后一个方案和几个具体示例,以此继续跟进。

在开始着手任何任务之前,强烈建议您按照一些初始建议进行操作:一次进行一项任务。我参与的工作包括:使用 EF4 的旧项目、庞大的模型和 ObjectContext API。尝试同时更改这三项只会导致挫折,甚至是更坏的结果。在这种情况下,我建议的过程是,首先更新 EF 版本,不要做出任何其他更改,然后确保一切可以继续正常运行。下一个步骤,确定可能被提取到新的小型模型的模型范围。但我首先允许新的模型继续将 ObjectContext API 作为目标。一切返回正常运行状态后,即开始转换到 DbContext API(仅针对该小型模型)。否则,在整个应用程序范围内将会出现大量错误,而要找到这些各种各样的错误将是一项艰巨且杂乱无章的任务。通过一次转换一个小型模型,您只有一小部分有错误的代码需要返工。您可以了解一些良好的借鉴和模式,从而使下一次转换小型模型更加顺利。

更新到更新版本的实体框架

由于 EF 团队将向后兼容性作为重点,从一个版本迁移到另一个版本只出现了最小的冲突。我将重点介绍如何更新到 EF6 — EF6 的主要版本及其小幅更新(如 EF6.02 和 EF6.1)。

从 EF4、EF4.1 或 EF5 迁移到 EF6 最严重的问题(在我看来并不是那么糟糕)来自于一些命名空间的更改。由于原始 API 仍在 Microsoft .NET Framework 中,因此,在 EF6 中进行复制可能会导致问题。因此在 EF6 API 中,这些类包含在与 System.Data 不同的命名空间中,以免发生冲突。例如,基于.NET 的 EF API 中有大量命名空间以 System.Data.Objects、System.Data.Common、System.Data.Mapping、System.Data.Query 及其他名称开头。还有一些类和枚举直接位于 System.Data 中,例如 System.Data.EntityException 和 System.Data.Entity­State。通过这种方式,将与 System.Data 直接相关的大部分类和命名空间移动到新的命名空间根路径 System.Data.Entity.Core。少量的类将移动到 System.Data.Entity 中,例如 EntityState 现在可在 System.Data.Entity.EntityState 中找到。举例说明,映射命名空间现在是 System.Data.Entity.Core.Mapping,而对象命名空间现在是 System.Data.Entity.Core.Objects。有关 System.Data.Entity.Core 未发生的特定异常,请参阅数据开发人员中心文档中的第 4 项“更新到 EF6”(bit.ly/OtrKvA)。

将现有应用程序更新到 EF6 时,我只是使编译器通过向我显示“未找到…类型或命名空间”错误来突出显示更改,然后进行解决方案范围内的查找和替换以更正命名空间。

例如,我从我的书籍《Programming Entity Framework》(实体框架编程)(第二版)中的小型示例解决方案开始介绍。此解决方案使用 EF4、EDMX 模型、生成代码的 POCO 实体类和 ObjectContext API 编写。Code First 和 DbContext API 不同时存在。在开始之前,我确定了应用程序仍在正常运行(在 Visual Studio 2013 中调试)。

尽管我关注的是较大的跳跃(从 EF4 到 EF6),但如果您是从 EF5 更新到 EF6,则需要遵循与命名空间修复相同的方法,因为这些命名空间更改发生在 EF5 和 EF6 之间。从 EF4 直接更新到 EF6 还面临一些其他挑战,并且我的旧解决方案使用了不包括直接替换的 T4 模板。

我从 EF4 更新到 EF6 的步骤

如果您习惯使用 NuGet 包分发获取实体框架,则需要回想 EF 仅作为 .NET Framework 的一部分且其所有的 DLL 都存在于 Windows 全局程序集缓存 (GAC) 中的情景。在更新到 EF6 之前,我手动删除了每个解决方案项目中对 System.Data.Entity(版本 4.0.0.0)的引用。还清理了解决方案(方法为:在解决方案资源管理器中右键单击解决方案,然后选择“清理解决方案”),以确认我可能强制放入 BIN 文件夹中的所有原始 DLL 均已不存在。事实上这是不必要的操作,因为 EF6 的 NuGet 包安装程序将为您删除旧引用。

之后我使用 NuGet 在相关项目中安装了 EF6,然后重新生成解决方案。您自己解决方案中的项目依赖性将加快命名空间问题的显露速度。在我的解决方案中,仅在一开始时显示了一个命名空间错误。我修复了该错误,并重新生成了解决方案,然后看到更多的错误,除了一个问题以外,其他所有的问题均为命名空间问题。对于这一个特例,编译器提供了一条有用的消息,指明我所使用的一个不常用属性 (EdmFunction) 的名称已被更改(并被标记为“已过时”),且已替换为属性“DbFunction”。

针对此小型解决方案在几分钟之内进行了一系列的迭代命名空间修复和重建之后,我可以成功构建和运行应用程序,即可以查看、编辑和保存数据。

修复 ObjectContext 和 POCO T4 模板

请记住,还有一个可能的任务。我的解决方案使用了 EDMX,这是一个使用 EF 设计器设计和维护的实体数据模型。由于我是在 Visual Studio 2010 中使用 EF4 创建的,因此它依赖于生成了 ObjectContext 的旧代码生成模板来管理所有数据的持久性和缓存。该模板从模型中的实体生成了 POCO 类(这些类没有实体框架的依赖关系)。如果我对模型进行任何更改,则需要重新生成类和上下文。但生成上下文的旧模板无法识别新命名空间。如果存在 DbContext 模板和 ObjectContext(及 EntityObjects)模板(请参阅图 1),则我的模板不提供 ObjectContext 和 POCO 组合的替换项。为让整个过程更有趣,我还自定义了使用的模板。我没有选择与我的应用程序不兼容的新模板,而是对存储在现有解决方案中的 Context.tt 模板进行了两处小更改:

  1. 在第 42 行,将“using System.Data.Objects;”更改为“using System.Data.Entity.Core.Objects;”
  2. 在第 43 行,将“using System.Data.EntityClient;”更改为“using System.Data.Entity.Core.EntityClient;”

You’ll Find Templates to Generate DbContext Plus POCOs or ObjectContext Plus Non-POCOs.
图 1 您将找到模板生成 DbContext 和 POCO 或 ObjectContext 和非 POCO。

现在,每次从模型生成类,ObjectContext 类都将获得正确的命名空间,并且我的自定义生成 POCO 的类将在应用程序中继续运行。请注意,我在前面提到的数据开发人员中心文档说明了如何使用支持的模板。

无需再更改任何代码即可获得的好处

我发现更新应用程序可使 EF6 的使用变得相当简单。然而,要考虑一个非常重要的因素。虽然应用程序现在使用的是最新版本的实体框架,但仅从实体框架的基本改进中获益,尤其是从 EF5 和 EF6 中获得一些出色的性能。由于从 EF5 中获得了大量的出色性能,不使用其他新功能即从 EF5 迁移到 EF6 不会受到太大影响。若要查看 EF6 的其他新增功能,请查看我 2013 年 12 月的一篇文章,标题为“实体框架 6:专家版本”(bit.ly/1qJgwlf)。许多改进是 DbContext API 和 Code First 的相关功能。如果您希望更新到 EF6,且还希望更新到 DbContext,我建议从简单地升级到 EF6 开始,等一切再次正常运行之后,再开始转换到 DbContext API。此更改更加复杂,我将在下一个专栏中详细介绍。

即便是使用这些可能是最小的更改,您的基本代码现在也已准备好利用更多新型 API。

分解大型模型

无论是使用了 EF 设计器创建模型,还是使用了 Code First 工作流(请参阅 bit.ly/Ou399J 中的“揭密实体框架策略:模型创建工作流”),其中具有多个实体的模型都可能导致设计时和运行时的问题。我的个人经验是大型模型不实用,且维护起来很困难。如果您使用的是设计器且包含多个实体(数百个),则不仅会使设计器打开和显示模型的耗时更长,而且还很难以可视化的方式导航模型。令人欣慰的是,设计器在 Visual Studio 2012 中获取了一些有帮助的强大功能(请参阅 bit.ly/1kV4vZ8 中的“实体框架设计器在 Visual Studio 2012 中功能增强”)。

即便如此,我始终建议使用较小的模型。在我的专栏“使用 DDD 界定的上下文收缩 EF 模型”(bit.ly/1isIoGE) 中,我谈到了小型模型的优势,以及在应用程序中使用这些小型模型的一些策略。如果您已经有一个大型模型,可能很难将其进行分解。我的客户使用了从大型数据库反向工程的模型,得到的结果是 700 个甚至是 1,000 个实体。无论是缩减设计器中的 EDMX 模型还是使用 Code First,都是一样的情况:分解是一项艰巨的任务。

以下是针对将大型模型分解为小型模型的一些有用的提示,以便进行更简便的维护,也可能会提供更好的运行时性能。

不要试图一次性重构整个模型。对于提取的每个小型模型,您还需要进行一些代码重构,这是因为更改了引用,而且您还可能要对一些关系代码进行推敲。

因此,第一步是确定基本独立的模型部分。起初不要担心重叠。例如,您可能运行的是制造和销售产品的公司的系统。软件可能包括维护销售团队(人员数据、联系信息、销售区域等)相关的功能。在根据销售区域的定义生成客户订单时,软件的另一部分可能会引用这些销售人员。但另一部分可能会跟踪销售人员的销售佣金。因此我们来处理一个特殊的情况,即一次性维护销售人员列表。

以下是转换到小型模型的步骤:

  1. 确定该方案中包含的实体(请参阅图 2)。
  2. 创建一个全新的项目。
  3. 在该项目中,(使用 EF 设计器或使用 Code First)定义一个新模型,该模型可识别相关实体。
  4. 确定与产品维护有关的应用程序代码。
  5. 更新使用原始上下文(进行查询、保存更改或其他功能)的相关代码,使其使用您创建的新的上下文。
  6. 重构现有代码,直到目标功能正常运行。如果进行了自动化测试,可能会更有效地完成重构任务。

在此过程中要记住一些其他的建议:

  • 请勿从大型模型中删除这些实体。如果这样做,会导致很多问题。就让它们留在那里,从现在开始忘记它们吧。
  • 记录您需要进行哪类重构才能使应用程序使用新的小型模型方面的相关日志。

SalesPerson and Territory Maintenance Can Be Extracted into a Separate Model with Little Impact to Other Entities
图 2 可以在对其他实体几乎没有影响的情况下将销售人员和区域维护提取为一个单独的模式

您将了解在从大型模型中继续分解其他聚焦式模型时可以应用的模式。

通过一次将一个新的小型模型反复添加到解决方案并指定代码使用,您会有更加愉快的体验。在大型模型中,这些相同的实体可能关系混乱,这与销售人员维护任务毫无关联。例如,您可能具有从销售人员到销售订单的关系,因此可能不希望从模型中完全删除销售人员。以下方式会更加简单:在一个模型中将简略的、只读的销售人员类型用作创建订单过程中的参考,然后在另一个模型中使用完备的、可编辑的销售人员类型(无法识别订单)进行维护。所有操作完成后,两个实体可以继续指向同一个数据库。如果您使用的是 Code First 和迁移,请查看上述有关缩减 EF 模型的文章,以在具有多个重叠模型时获取共享数据库方面的指导。

您最终可能具有多个小型模型,且仍然有对大型模型的引用。此时,确定大型模型中不再需要且可以安全删除的实体就会更加容易。

耐心:不错,这非常有益

最重要的是要少量进行更新和重构,每重复一次,都需要再次进行测试并使应用程序正常运行,然后才能继续进行。请将更新到更新版本的实体框架视为其自身的工作项。这也可以一分为二,首先集中精力获取更新的 API,并确保代码继续运行,然后再更改代码以使用新功能。相同的初始步骤适用于分解大型模型,尤其适用于您计划从 ObjectContext 更新到 DbContext 的情况。提取小型模型并进行重构,以确保相关逻辑可以使用新的小型模型来运行。正常运行后,就该是与 ObjectContext 分离的时候了,这会首先分解一组代码。但至少分解的代码将隔离到基本代码中较小的区域,这样您每次就可以重构较少的代码。

在我的下一个专栏中,将深入探讨一个更具挑战性、但完全可以实现的目标:移动 ObjectContext 代码以使用 DbContext API。

Julie Lerman* 是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她是《Programming Entity Framework》(2010) 以及“代码优先”版 (2011) 和 DbContext 版 (2012)(均出自 O’Reilly Media)的作者,博客网址为 thedatafarm.com/blog。通过她的 Twitter(网址为 twitter.com/julielerman)关注她,并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。*

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