使用自定义基数据上下文进行 LINQ 事务






4.75/5 (5投票s)
如何使用自定义基数据上下文简化 LINQ 事务
引言
LINQ to SQL 通过弥合业务逻辑和数据库之间的差距,使开发人员能够快速开发数据驱动的应用程序。
LINQ to SQL 系统的一个核心组件是 DataContext
。
DataContext 是映射到数据库连接的所有实体的来源。它跟踪您对所有检索到的实体所做的更改,并维护一个“身份缓存”,该缓存保证多次检索到的实体由同一个对象实例表示。通常,DataContext 实例的设计寿命为一个“工作单元”,但您的应用程序如何定义这个术语。DataContext 轻量且创建成本不高。典型的 LINQ to SQL 应用程序在方法范围内创建 DataContext 实例,或作为代表一组逻辑相关数据库操作的短暂类的成员。
LINQ to SQL 支持三种不同的事务模型。以下列表按执行检查的顺序列出了这些模型。
-
显式本地事务
当调用
SubmitChanges
时,如果Transaction
属性设置为 (IDbTransaction
) 事务,则SubmitChanges
调用将在同一事务的上下文中执行。您有责任在事务成功执行后提交或回滚事务。与事务对应的连接必须与用于构造
DataContext
的连接匹配。如果使用了不同的连接,则会引发异常。 -
显式分布式事务
您可以在活动的
Transaction
上下文中调用 LINQ to SQL API(包括但不限于SubmitChanges
)。LINQ to SQL 检测到调用处于事务上下文中,因此不会创建新事务。在这种情况下,LINQ to SQL 也会避免关闭连接。您可以在此类事务的上下文中执行查询和SubmitChanges
。 -
隐式事务
当您调用
SubmitChanges
时,LINQ to SQL 会检查调用是否处于Transaction
上下文中,或者Transaction
属性 (IDbTransaction
) 是否设置为用户启动的本地事务。如果两者都没有找到,LINQ to SQL 会启动一个本地事务 (IDbTransaction
) 并使用它来执行生成的 SQL 命令。当所有 SQL 命令都成功完成后,LINQ to SQL 会提交本地事务并返回。
总结
- LINQ 在调用
SubmitChanges
时会为您创建Transaction
。如果您需要更改事务隔离级别怎么办?如果您需要执行一个带有 (NOLOCK
) 子句的查询怎么办?(ReadUncommitted
) 您如何使用默认的DataContext
来完成此任务?虽然可以做到,但我们需要一个可重用的解决方案。 - 使用
System.Transactions
是一种优雅的方法,也是分布式事务的唯一选择。如果您选择将所有事务包装在System.Transactions
块中,那么您可能不需要为DataContext Transactions
制定解决方案。 - 如果您手动启动
Transaction
,您必须关闭它。连接也是如此。在创建此类时,这是一个需要记住的重要点。
背景
我需要一种简单的方法来确保执行 LINQ to SQL 查询的适当隔离级别,因此有了这个类。有时执行“脏读”正是期望的效果,而 BaseDataContext
允许我实现这一点。
Using the Code
BaseDataContext
可以通过三种方式使用:
- 使用 *.dbml 文件时,您可以将
DataContext
的基类设置从DataContext
更改为BaseDataContext
。如果您只需要对Context
进行“脏读”(Uncommitted
),这是最简单的实现方法。 - 如果您需要更多地控制
DataContext
,可以扩展由 *.dbml 文件创建的偏部分类。
public partial class NorthwindDataContext : BaseDataContext { public NorthwindDataContext(string connectionString, IsolationLevel level) : base(connectionString, level) { } }
- 如果您不希望使用 *.dbml 文件创建的默认
Context
,则可以直接继承BaseDataContext
。
我上传了一个新版本,它只会在需要时打开连接/事务。
这确保了连接/事务能及时关闭。
- 您应该始终将
BaseDataContext
包装在using
语句中
using (var Ctx = new NorthwindDataContext (ConnectionString, IsolationLevel.ReadCommitted) { LoadOptions = NorthwindLoadOptions.LoadCustomerWithOrders }) {
- 连接在
Dispose
期间关闭。待处理的Transaction
也会在此期间提交。
确保正确的事务级别
我们如何才能确信我们的基类正确地设置了事务级别?
我们可以使用 SQL Profiler 并确保调用被包装在适当的 Transaction
中。
我们来看一些代码
using (var Ctx = new NorthwindDataContext
(ConnectionString, IsolationLevel.ReadUncommitted)
{ LoadOptions = NorthwindLoadOptions.LoadCustomerWithOrders })
{
Item = Ctx.Customers.First();
Item.ContactName = "Rocks";
Ctx.SubmitChanges();
}
很简单。它读取一条记录,更改 contactname
,然后将更改提交给 context
。在 using
语句中,我们指定事务级别为 ReadUncommitted
。这意味着我们希望进行脏读。
在连接字符串中,我通过设置 App='DTX'
属性来指定应用程序的名称。这将允许我在 profiler 中过滤掉任何无关数据,并查看我的基类正在做什么。

成功!我们看到 SET TRANSACTION LEVEL READ UNCOMMITTED
行在我们执行任何读/写操作之前。
那么已提交读呢?
using (var Ctx = new NorthwindDataContext(ConnectionString,
IsolationLevel.ReadCommitted)
{ LoadOptions = NorthwindLoadOptions.LoadCustomerWithOrders })
{
Item = Ctx.Customers.Where(p => p.ContactName == "Rocks").First();
Item.ContactName = string.Format("LinqToSql - {0}",
DateTime.Now.Second.ToString());
Ctx.SubmitChanges();
}
再次,简单的 LINQ 语法。
让我们检查 Profiler。

已提交读!完美。
大获成功!
关注点
我计划通过本网站和 我的博客 来保持更新。
使用 System.Transactions
对象来处理分布式事务。此类专为不需要分布式事务的项目设计。
历史
- 版本 1.0. - 使用自定义
DataContext
对 LINQ 进行事务支持 - 版本 1.1. 不那么急切 = 更好。更改了连接/事务的打开/关闭方式/时间