Entity Framework 的泛型数据管理器
通用数据管理器提供针对实体量身定制的、线程特定的 IUnitOfWork 存储库,并以线程安全的方式处理所有底层连接和清理工作。
引言
您是否曾尝试从不同的线程使用 DbContext(Entity Framework 中相当于 ADO.NET 的 DbConnection)?如果您尝试过,您一定知道它会抛出各种奇怪而神秘的错误消息。从这些消息中您只能理解一点:在不特别注意的情况下,您无法轻松地从多个线程使用 Entity Framework。
在本文中,我将介绍一个线程安全的数据管理器,您不仅可以将其传递给您的线程,它还为您提供了各种其他功能,让您不必执行创建存储库和 IUnitOfWork 实现等琐碎任务。
背景
Entity Framework 是微软针对 NHibernate、Telerik 的 DataAccess 和各种其他对象关系映射框架的解决方案。它内部使用相同的 IDbConnection、IDbCommand 等,但提供了一种在 C# 代码中指定查询的便捷方式,而不是使用 SQL。您可以直接使用 Entity Framework (EF)。只需实例化 DbContext,但建议您使用存储库模式和 IUnitOfWork。
在此模式下,您需要为每个实体实现基本功能,如 GetAll()、Add()、Update()、Delete(),这样您就可以利用这些基本功能构建复杂的功能,同时将所有数据库逻辑封装在存储库中。这个想法很有趣,但如果您有 30 多个实体怎么办?实践是为每个实体创建一个单独的存储库,一遍又一遍地编写相同的代码。聪明人可能会使用通用函数等来减少这项任务,但想法是一样的,为每个实体拥有一个单独的存储库。
那么多线程呢?随着框架版本的不断增加,多线程变得越来越容易理解和使用。为什么不呢,这样任务就能更快地完成。对于那些多线程并依赖 Entity Framework 来存储或检索信息到数据库的应用程序。假设您有一个应用程序需要处理一些电子提交的表单,提取信息,将其保存到数据库并读取下一个表单。与此同时,您需要扫描新添加的记录,提取一些信息并通知一些订阅者。您可以使用来自这些线程的存储库吗? 可以,也可以不行。
可以,如果您在线程中创建存储库,使用它,并在那里即时释放它。不行,您不能在一个线程中使用另一个线程的存储库。DbContext 不是线程安全的。如果您尝试这样做,您会遇到各种异常,让您一整天都在感谢谷歌博士的知识。
什么是通用数据管理器?
通用数据管理器,一个开源库,试图为这些问题提供解决方案。其理念是:“只关注做什么,而把怎么做交给库”。该库不仅为您提供了所有实体的存储库模式实现,还以自己的方式提供了线程安全性。管理器负责处理释放和其他事情。
管理器实现了 IDataRepositoryProvider 接口。该接口为您提供实体的存储库。管理器是线程安全的,但它提供的存储库不是线程安全的。您可以将管理器在线程之间传递,它们会请求存储库。管理器会为该线程创建一个特定的 DbContext,创建一个使用该 DbContext 的存储库并将其返回。管理器会维护一个 DbContext、其关联存储库以及它们关联的线程的列表。如果该线程再次请求存储库,并且该线程已经有一个 DbContext,管理器会简单地创建一个存储库,将其绑定到现有上下文并返回。您使用存储库,一旦完成,请调用存储库的 Dispose。即使您忘记调用 Dispose,管理器也会处理它,前提是您使用的是特定于该情况的执行策略。
连接参数
这是一个数据管理器连接数据库、构建 DbContext 和绑定实体存储库所需的结构。它有三个成员:
- 连接字符串:一个简单的旧式连接字符串,不包含实体模型信息,以便于提供信息
- 模型名称:这是您为模型指定的名称,不要将其与模型文件名混淆。
- 延迟:这是一个时间跨度,用于指定延迟。此延迟用于各种内部等待和休眠功能。如果您不指定,默认值为 30 秒。
- 提供程序名称:这是您正在使用的数据库的提供程序名称,默认值为“System.Data.SqlClient”,即 SQL Server。
此结构位于 GenericDataManager.Common 命名空间中。这是必需的两个强制参数之一。 将其视为您的模型和数据库信息。
什么是执行策略?
该库提供了 2 个数据管理器。
- 旧式数据管理器
- 带策略的数据管理器(*建议您将此管理器用于您的编程需求*)
旧式数据管理器从一开始就存在。当我最初创建该库时,它就是库中唯一的内容,没有太多自定义。它仍然存在是为了向后兼容。目前,库版本 2 正处于 beta 测试阶段,但相当稳定。新版本具有带策略的数据管理器,这是一种提供大量自定义的实现,而这种自定义是通过执行策略完成的。
执行策略控制的大多数内容都会影响数据管理器在涉及多个线程时的行为。这些包括但不限于:
- DbContext 如何与其线程相关联
- 存储库应被视为使用多长时间。
- 何时是关闭 DbContext 并销毁其关联存储库的最佳时机,
- 清理器(一个守护线程,可以将其视为数据管理器的垃圾清理器)应调用多少次。
- 清理器应如何执行其任务
对于单线程应用程序,您不需要太多自定义执行策略。只需实例化默认的并传递它。它不需要任何额外的参数或其他信息。
什么是清理器?
在内部,数据管理器维护一个线程安全列表,其中包含有关线程、其 DbContext 和与之关联的存储库的信息。这被称为上下文映射。这个上下文映射是数据管理器(无论是旧式还是基于策略的)的代码。清理器是一个后台线程,其工作是执行上下文映射的清理,并删除不需要的 dbcontexts 和存储库。要完全理解它,您需要理解数据管理器的工作原理。
当您请求某个实体的存储库时,数据管理器会检查上下文映射,以查看您正在调用的线程是否已存在。如果线程存在,它会检查其拥有的 DbContext 是否处于活动状态,并扫描其拥有的存储库列表。如果您请求的实体存储库存在,它将简单地返回该存储库。您使用它并释放它。
如果您进行请求的线程不存在,数据管理器会在该线程上创建一个 DbContext,并在上下文映射中记录下来,同时记录线程。由于 DbContext 是新创建的,它会创建存储库,将其与 DbContext 关联(这意味着有一个存储库列表与每个 DbContext 相关联),然后将其返回给您。您使用它,然后将其释放,或者可能忘记释放它。我们稍后将看到这种情况也能轻松处理。
当您使用策略创建数据管理器时,它还会创建一个后台线程,该线程的唯一任务是定期扫描上下文映射,并清除未使用的 dbcontexts 和存储库以保持其清洁。在指定的间隔,清理器会扫描上下文映射,收集未使用的 dbcontexts。一旦所有都被标记,它们就会从上下文映射中删除。数据管理器在没有任何中断的情况下继续工作,而清理器则逐个单独地释放连接。
执行策略有各种参数和策略会影响清理器的工作。例如,有一个策略规定,一旦线程死亡,就没有必要保留 DbContext 了,因此一旦线程完成,即使您忘记释放任何存储库,数据管理器也会关闭其 DbContext 并释放所有存储库。但在此处要小心,不要使用此策略来弥补不良编程习惯。
我们将在下一篇文章中详细讨论执行策略和清理器,届时我们将利用多线程。
使用数据管理器
先决条件
使用代码非常简单。我在此做一些假设:
- 您正在使用 Visual Studio 2012(最低版本)。社区版也可以。
- 您正在使用 SQL Server 2005(最低可能版本)。Express 版也可以。
- 您对 Entity Framework 有基本了解
- 您对多线程有基本了解。
- 您对存储库和 IUnitOfWork 有基本了解。
数据库及数据
我将遵循数据库优先的方法。所有这些意味着我将先创建数据库,然后基于它创建我的模型。为了节省本文的篇幅,请运行 **SampleDatabaseScript.sql**。它将创建必要的表并填充一些示例数据,以便您可以快速开始。假设您的数据库是空的,并且一切都执行成功,那么在执行脚本后您的数据库将如下所示:
Entity Framework 模型
现在我们将创建一个包含 Entity Framework 模型(基于我们之前创建的数据库)的项目。启动 Visual Studio,选择新建项目,类库类型。 您可以选择任何喜欢的名称,为了本文的目的,我将其命名为“Common Model”。 类库,您将获得一个骨架应用程序。删除 Class1.cs 文件。我们不需要它。 此时您的项目应该看起来像这样:
我们需要向其中添加一个模型,该模型引用我们刚才创建的数据库。转到菜单 **项目** 并选择 **添加新项...** 使用以下设置添加新的实体模型,
- 模型名称: **MyModel**
- 从数据库生成的 EF 设计器
- Entity Framework 6.0
- 模型命名空间: **CommonModel**
- 选择表: **Department**, **Employee**
删除 MyModel.Context.tt,我们不需要它。 通用数据管理器将动态创建它。现在进行构建,如果您一路没有遇到任何错误,它应该看起来像这样。
我们继续下一步。
简单的应用程序
现在我们将创建一个简单的应用程序,当然是单线程的,来使用通用数据管理器。我们有一些初步的,或者更确切地说,一些必要的步骤,例如创建应用程序,添加 NuGet 包,添加对我们的实体模型的引用。
- 右键单击解决方案,**添加 | 新建项目...** 并选择控制台应用程序。
- 要添加通用数据管理器,请右键单击您的控制台应用程序,选择 **管理 NuGet 程序包...** 转到浏览选项卡并搜索 Generic Data Manager。它应该会列出,选择它。(您无需单独安装 Entity Framework 包。一旦安装了 Generic Data Manager,它会自动安装)。您也可以使用程序包管理器控制台并使用命令进行安装:**Install-Package GenericDataManager -Pre**
- 通过右键单击并选择“**设置为启动项目**”,将您的控制台应用程序设置为启动项目。 您需要引用包含实体模型的项目在您的控制台应用程序中。
- 构建您的解决方案以验证到目前为止一切正常。
所有这些步骤都是准备工作。现在一切都已设置好,我们准备使用通用数据管理器,看看它为我们准备了什么。
使用通用数据管理器
使用数据管理器非常简单易用。首先,我们现在添加必要的引用。
using GenericDataManager;
using GenericDataManager.Common;
using GenericDataManager.Interfaces;
要使用数据管理器,您需要四样东西:
- 简单的连接字符串
- 您的模型名称
- 提供程序名称(SQL Server,即 System.Data.SqlClient 是默认的)
- 执行策略
如果您曾经使用过 Entity Framework 的连接字符串,它们似乎极其复杂,它们不仅提供连接字符串和提供程序的信息,还提供实体模型文件的信息。通用数据管理器 (GDM) 让您摆脱了所有这些。它只需要一个简单的连接字符串、一个提供程序名称和一个模型名称。这就是我们要做的。实例化它并提供必要的信息。 在 main() 中添加以下行:
//Be sure to provide your own data source and your database name
var connection = new ConnectionParameters(
"data source=<yourSource>;initial catalog=<your DB>;integrated security=True;",
"MyModel");
var policy = new ExecutionPolicy();
IDataRepositoryProvider manager = new GenericDataManager.DataManagerWithPolicy(connection, policy);
为了保持简单,我们将使用默认的执行策略。在通用数据管理器中,接口至关重要。这里有一个重要提示:大多数情况下,接口是显式实现的,所以**除非您将对象转换为正确的接口,否则您将看不到必要的功能**。现在您已经准备好 **IDataRepositoryProvider** 数据管理器了,让我们开始使用它。
using(var repository = manager.Repository.Get <Common_Model.Employee>())
{
foreach (var emp in repository.All(x=>x.FullName.StartsWith("E")))
Console.WriteLine("{0} {1}", emp.FullName, emp.Email);
}
我们请求了位于 Common_Model 命名空间中的 Employee 实体的存储库。IDataRepositoryProvider 具有 Repository 属性,该属性实际上为您提供了您想要的存储库类型以及您需要的实体。存储库将为您提供以下功能:
- One:根据 lambda 表达式检索单个记录。
- All:检索记录列表,根据 lambda 表达式,如果未指定,则检索所有记录。
- Count:计算记录数,提供 lambda 表达式,它只计算符合条件的记录。
- Exists:检查是否有任何记录符合条件。
- Add:支持批量操作和 lambda 操作。
- Update:支持批量操作和 lambda 操作。
- Delete:支持批量操作和 lambda 操作。
所以您不必编写太多代码,正如前面提到的:专注于您想做什么,而不是怎么做。有了这些基本功能,您就可以构建复杂的函数。我们只检索名字以字母 E 开头的员工,并显示他们的姓名和电子邮件地址。
在下一篇文章中,我们将讨论如何将其用于多线程并使用自定义执行策略。
源代码、文档和 NuGet 包
- 通用数据管理器是一个开源库,所有**源代码**均可在 GitHub 上找到:https://github.com/MercedeX/GenericDataManager
- 必要的**文档**可在 https://github.com/MercedeX/GenericDataManager/wiki 找到。
- **NuGet 包**可在 https://nuget.net.cn/packages/GenericDataManager 上找到。
- 在 https://github.com/MercedeX/GenericDataManager/issues **报告库中的问题**。
请告诉我您的评论和观点。您的评论将帮助我改进库。