65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (5投票s)

2008 年 4 月 6 日

CPOL

4分钟阅读

viewsIcon

95376

downloadIcon

791

如何使用自定义基数据上下文简化 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 会提交本地事务并返回。

总结

  1. LINQ 在调用 SubmitChanges 时会为您创建 Transaction。如果您需要更改事务隔离级别怎么办?如果您需要执行一个带有 (NOLOCK) 子句的查询怎么办?(ReadUncommitted) 您如何使用默认的 DataContext 来完成此任务?虽然可以做到,但我们需要一个可重用的解决方案。
  2. 使用 System.Transactions 是一种优雅的方法,也是分布式事务的唯一选择。如果您选择将所有事务包装在 System.Transactions 块中,那么您可能不需要为 DataContext Transactions 制定解决方案。
  3. 如果您手动启动 Transaction,您必须关闭它。连接也是如此。在创建此类时,这是一个需要记住的重要点。

背景

我需要一种简单的方法来确保执行 LINQ to SQL 查询的适当隔离级别,因此有了这个类。有时执行“脏读”正是期望的效果,而 BaseDataContext 允许我实现这一点。

Using the Code

BaseDataContext 可以通过三种方式使用:

  1. 使用 *.dbml 文件时,您可以将 DataContext 的基类设置从 DataContext 更改为 BaseDataContext。如果您只需要对 Context 进行“脏读”(Uncommitted),这是最简单的实现方法。
  2. 如果您需要更多地控制 DataContext,可以扩展由 *.dbml 文件创建的偏部分类。

    public partial class NorthwindDataContext : BaseDataContext
    {
        public NorthwindDataContext(string connectionString, IsolationLevel level) :
            base(connectionString, level) { }
    } 
  3. 如果您不希望使用 *.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 中过滤掉任何无关数据,并查看我的基类正在做什么。

ReadUncommitted.JPG

成功!我们看到 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。

ReadCommitted.JPG

已提交读!完美。

大获成功!

关注点

我计划通过本网站和 我的博客 来保持更新。

使用 System.Transactions 对象来处理分布式事务。此类专为不需要分布式事务的项目设计。

历史

  • 版本 1.0. - 使用自定义 DataContext 对 LINQ 进行事务支持
  • 版本 1.1. 不那么急切 = 更好。更改了连接/事务的打开/关闭方式/时间
© . All rights reserved.