TSQL中的事务处理、ADO.NET中的事务处理、LINQ to SQL中的隐式事务处理、分布式事务处理

测试数据库

数据库名称:test

数据库表名称:Users

表结构:

列名
 数据类型
 允许为空
 注释
 
UsersID
 int
 false
 主键,自增标识
 
UserName
 varchar(50)
 false
 用户名,唯一约束
 
Password
 varchar(50)
 false
  

在文章开始之前,我们首先要明确:事务是单个的工作单元。如果某一事务成功,则在该事务中进行的所有数据修改均会提交,成为数据库中的永久组成部分。如果事务遇到错误且必须取消或回滚,则所有数据修改均被清除。也就是说,当我们使用SQL Server的查询分析器中执行了了一行代码“insert into t values(x, y, z)”,数据成功地添加到了数据库中,但这并不是说这里没有使用事务处理,而是系统帮我们将常用的操作自动地做了一些处理(我猜想应该是代理模式)。

SQL Server数据库为我们提供了以下几种事务模式:

自动提交事务 每条单独的语句都是一个事务。

显式事务 每个事务均以 BEGIN TRANSACTION 语句显式开始,以 COMMIT 或 ROLLBACK 语句显式结束。

隐式事务 在前一个事务完成时新事务隐式启动,但每个事务仍以 COMMIT 或 ROLLBACK 语句显式完成。

批处理级事务 只能应用于多个活动结果集 (MARS),在 MARS 会话中启动的 Transact-SQL 显式或隐式事务变为批处理级事务。当批处理完成时没有提交或回滚的批处理级事务自动由 SQL Server 进行回滚。

第一种:T-SQL中的事务处理

下面代码创建一个T-SQL语句的显式事务

--开启一个事务
begin transaction
    --使用try…catch结构捕获异常
    begin try
        --插入两条数据(相同的用户名)
        INSERT INTO [test].[dbo].[Users]
                   ([UserName]
                   ,[Password])
             VALUES
                   ('张三','123')
        INSERT INTO [test].[dbo].[Users]
                   ([UserName]
                   ,[Password])
             VALUES
                   ('张三','456')
        commit transaction --提交事务
    end try
    --如果出现了异常,进入catch代码段
    begin catch
        rollback transaction --回滚事务
        select ERROR_MESSAGE() as [Message] --输出错误信息
    end catch

以上代码执行后会失败,错误信息为:“违反了 UNIQUE KEY 约束 'UQ_Users_UserName'。不能在对象 'dbo.Users' 中插入重复键”。

需要注意的是,在SQL Server中,如果没有显式声明事务,那么系统将分配隐性事务。当数据库引擎 实例首次执行下列任何语句时,都会自动启动一个事务:

ALTER TABLE
 INSERT
CREATE
 OPEN
DELETE
 REVOKE
DROP
 SELECT
FETCH
 TRUNCATE TABLE
GRANT
 UPDATE

下面看ADO.NET中的事务处理是怎样的?

第2、ADO.NET中的事务处理

以下代码演示了如何使用SqlClient组件来创建一个显式事务:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    SqlCommand command = connection.CreateCommand();
    SqlTransaction transaction;
    // 启动一个本地事务
     transaction = connection.BeginTransaction();
    // 在启动一个本地事务之前,需要为Command对象指派Connection对象与Transaction对象
     command.Connection = connection;
    command.Transaction = transaction;
    try
    {
        command.CommandText =
            "INSERT INTO [test].[dbo].[Users] ([UserName] ,[Password]) VALUES ('张三','123')";
        command.ExecuteNonQuery();
        command.CommandText =
            "INSERT INTO [test].[dbo].[Users] ([UserName] ,[Password]) VALUES ('张三','123')";
        command.ExecuteNonQuery();
        // 尝试提交事务
         transaction.Commit();
        Console.WriteLine("所有的记录已经提交至数据库");
    }
    catch (Exception ex)
    {
        Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
        Console.WriteLine("异常信息: {0}", ex.Message);
        // 尝试回滚事务
        try
        {
            transaction.Rollback();
        }
        catch (Exception ex2)
        {
            // 此catch块将处理任何可能发生在服务器上的,导致回滚失败的错误。如连接已关闭。
             Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
            Console.WriteLine("  Message: {0}", ex2.Message);
        }
    }
}

以上代码执行后会失败,错误信息为:“违反了 UNIQUE KEY 约束 'UQ_Users_UserName'。不能在对象 'dbo.Users' 中插入重复键”。

看完ASP.NET的事务处理后,接着请看LINQ to SQL中的隐式事务处理

第3、LINQ to SQL中的隐式事务处理

下面的代码演示了如何使用LINQ to SQL技术进行事务处理:

using (TestDataContext db = new TestDataContext())
{
    // 在控制台中输出T-SQL语句
    db.Log = Console.Out;
    //创建两个Users对象(注意,用户名是相同的)
    Users u1 = new Users()
    {
        UserName = "张三",
        Password = "123"
    };
    Users u2 = new Users()
    {
        UserName = "张三",
        Password = "456"
    };

    try
    {
        db.Users.InsertAllOnSubmit(new Users[] { u1, u2 });
        // 尝试提交事务
         db.SubmitChanges();
        Console.WriteLine("所有的记录已经提交至数据库");
    }
    catch (Exception ex)
    {
        Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
        Console.WriteLine("异常信息: {0}", ex.Message);
    }
}

以上代码执行后会失败,错误信息为:“违反了 UNIQUE KEY 约束 'UQ_Users_UserName'。不能在对象 'dbo.Users' 中插入重复键”。

在代码中,当调用 SubmitChanges 时,LINQ to SQL 会检查此调用是否在 Transaction 的作用域内或者 Transaction 属性 (IDbTransaction) 是否设置为由用户启动的本地事务。如果这两个事务它均未找到,则 LINQ to SQL 启动本地事务 (IDbTransaction),并使用此事务执行所生成的 SQL 命令。当所有 SQL 命令均已成功执行完毕时,LINQ to SQL 提交本地事务并返回。这就是LINQ to SQL的“隐式事务”。

介绍完以上三种事务处理,最后我们来看下分布式事务处理又是怎样的?

第4、分布式事务处理

本文进行到这里,将使用一个问题引入标题内容“分布式事务处理”是怎样的一种操作:如何将两个DataContext对象的提交过程当做一个事务来处理?

可能你对这个问题不是太明白,下面的代码演示了这个问题是一个怎样的过程:

//创建两个TestDataContext对象
TestDataContext db1 = new TestDataContext();
TestDataContext db2 = new TestDataContext();
//创建两个Users对象(注意,用户名是相同的)
Users u1 = new Users()
{
    UserName = "张三",
    Password = "123"
};
Users u2 = new Users()
{
    UserName = "张三",
    Password = "456"
};
try
{
    //将两个Users对象分别加入到不同的TestDataContext对象中
     db1.Users.InsertOnSubmit(u1);
    db2.Users.InsertOnSubmit(u2);
    // 尝试提交事务
     db1.SubmitChanges();
    db2.SubmitChanges();
    Console.WriteLine("所有的记录已经提交至数据库");
}
catch (Exception ex)
{
    Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
    Console.WriteLine("异常信息: {0}", ex.Message);
}

以上代码执行后同样会失败,错误信息为:“违反了 UNIQUE KEY 约束 'UQ_Users_UserName'。不能在对象 'dbo.Users' 中插入重复键”。但是明显的,数据库中将成功添加一条数据——db1.SubmitChanges()执行时提交的数据。

那么,如何才能让这个过程符合我们的要求呢?以下代码演示了这一点:

//创建两个TestDataContext对象
TestDataContext db1 = new TestDataContext();
TestDataContext db2 = new TestDataContext();
//创建两个Users对象(注意,用户名是相同的)
Users u1 = new Users()
{
    UserName = "张三",
    Password = "123"
};
Users u2 = new Users()
{
    UserName = "张三",
    Password = "456"
};
//将两个Users对象分别加入到不同的TestDataContext对象中
db1.Users.InsertOnSubmit(u1);
db2.Users.InsertOnSubmit(u2);
// 使用TransactionScope对象,使代码块成为事务性代码。
using (TransactionScope rs = new TransactionScope())
{
    try
    {
        // 尝试提交事务
         db1.SubmitChanges();
        db2.SubmitChanges();
        rs.Complete();
        Console.WriteLine("所有的记录已经提交至数据库");
    }
    catch (Exception ex)
    {
        Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
        Console.WriteLine("异常信息: {0}", ex.Message);
    }
}

当决定使用SQL Server分布式事务处理模式时,必须启动名称为Distributed Transaction Coordinator的Windows服务项,你可以在服务管理器中启动它(开始菜单-运行- services.msc),也可以在命令提示符中直接启动它(net start msdtc)。

这时,包含在TransactionScope对象代码块中的两个DataContext对象,其执行SubmitChanges方法时将处于事务之中,如果此时出现了异常,那么之前的所有操作将会回滚。也就是说如果在事务范围中(即从初始化 TransactionScope 对象到调用其 Dispose 方法之间)发生异常了异常,事务都将结束,并回滚至初始状态。这意味着,即使执行了Complete方法,在这之后发生的异常也将导致事务失败。所以,尽量不要在执行Complete方法与Dispose方法之间,加入可能会导致事务失败的代码。

如果TransactionScope对象成功执行了Complete方法,那么所有数据将提交至数据库中。需要注意的是,如果在TransactionScope对象代码块中未执行TransactionScope对象的Complete方法,那么事务也将终止并回滚至初始状态。

同样的,TransactionScope对象代码块也适用于其他代码级数据库访问,例如将多个SqlConnection对象的提交过程放在同一个TransactionScope对象代码块中,就可以方便地控制事务的执行方式。

原文地址:https://www.cnblogs.com/tianguook/p/1890814.html