摘要:C#环境中的事务Transaction示例
为图方便省事,你可以在一个Command对象的CommandText属性指定多条以;分割的语句。这种情况下若没有事务,所有的语句都会被执行,若其中有语句出错,就导致了数据的不一致性。
当然我们也可以写存储过程,在SQLServer的数据库系统内建存储过程的语句若没有事务,多条语句中的部分语句失效,一样导致数据的不一致性:你可以在存储过程内部Try/Catch/BeginTransaction等。
Connection对象BeginTransaction启动事务,然后将事务赋值给Command对象的Transaction属性即挂接了事务。即使没有Commit 和Rollback,若执行中出现错误,事务一样自动回滚,或者成功提交。
BeginTransaction可以指定隔离级别。ReadXXX不会对数据库加锁,尽管在事务中,外部程序仍然可以读取数据;但若事务中有Update语句,将导致数据库锁,外部程序不能继续读取数据。
尽量考虑在存储过程中使用事务,避免使用ADO的事务,因为ADO的事务可能导致数据库长时间处于锁定状态;而数据库内的存储过程中的事务往往不会长时间挂起事务。
下面是简单的示例,供参考:
///
/// ADO事物处理:单一Command对象执行多条语句:
/// 包括导致异常的语句的后续语句,都将被执行,若没有Transaction,修改为15。
///
private static void TransByADO()
{
string connect = "Data Source=.\\sqlexpress; Initial Catalog=NorthWind ; Integrated Security = true;";
//Old value: UnitPrice=18
string update = "Update Northwind.dbo.Products set UnitPrice=10 where ProductID=1;";
update += "update Products set unitPrice=unitprice-18 where productID=1;"; //因CHECK约束,将失败
update += "update Products set unitPrice=15 where productID=1";
SqlTransaction tranProducts = null;
using (SqlConnection cnNorthwind = new SqlConnection(connect))
{
try
{
cnNorthwind.Open();
tranProducts = cnNorthwind.BeginTransaction();//--1
SqlCommand cmdUpdate = new SqlCommand(update, cnNorthwind);
cmdUpdate.Transaction = tranProducts;//-2
cmdUpdate.ExecuteNonQuery();
//如果没有Commit()和Rollback(),事物仍然生效,结果为原始值18
tranProducts.Commit(); //--3
}
catch (SqlException ex)
{
tranProducts.Rollback();//--4
//throw ex;
Console.WriteLine(ex);
}
finally
{
cnNorthwind.Close();
}
}
}
///
/// 存储过程中若有部分语句执行失败,在没有Transaction的情况下将导致数据不一致。
//create procedure TransTest
//as
//begin
// update Products set unitPrice=10 where productID=1
// update Products set unitPrice=unitprice-18 where productID=1
// update Products set unitPrice=15 where productID=1
//end
//go
///
private static void TransByStoreProcedure()
{
string connect = "Data Source=.\\sqlexpress; Initial Catalog=NorthWind ; Integrated Security = true;";
using (SqlConnection cnNorthwind = new SqlConnection(connect))
{
SqlTransaction tranProducts = null;
try
{
cnNorthwind.Open();
tranProducts = cnNorthwind.BeginTransaction();//-----1
SqlCommand cmdProduct = new SqlCommand("TransTest", cnNorthwind);
cmdProduct.CommandType = CommandType.StoredProcedure;
cmdProduct.Transaction = tranProducts; //----2
cmdProduct.ExecuteNonQuery();
//如果没有Commit()和Rollback(),事物仍然生效,结果为原始值18
tranProducts.Commit(); //----3
}
catch (SqlException ex)
{
tranProducts.Rollback(); //----4
Console.WriteLine(ex.ToString());
//throw ex ;
}
finally
{
cnNorthwind.Dispose();
}
}
}
///
/// BeginTransaction(IsolationLevel)定义事务的隔离级别
/// ReadCommitted:默认项,读取时加共享锁,避免脏读,数据在事务完成前可修改,可被外部读取
/// ReadUncommitted:可脏读,不发布共享锁,也无独占锁
///
private static void TransDebug()
{
string connect = "Data Source=.\\sqlexpress; Initial Catalog=NorthWind ; Integrated Security = true;";
using (SqlConnection cnNorthwind = new SqlConnection(connect))
{
SqlCommand cmdProducts = null;
SqlTransaction tranDebug = null;
try
{
cnNorthwind.Open();
tranDebug = cnNorthwind.BeginTransaction(IsolationLevel.ReadUncommitted);//可读未提交数据
cmdProducts = new SqlCommand("select * from products where ProductID=1", cnNorthwind);
cmdProducts.Transaction = tranDebug;
cmdProducts.ExecuteNonQuery();
//下面行下断点,在MSMS查看数据记录是否可修改,并尝试修改:发现能修改数据
cmdProducts.CommandText = "Update products set UnitPrice=UnitPrice-18 where ProductID=1";
cmdProducts.ExecuteNonQuery();
//下面行下断点,因上面的Update导致数据被锁,无法在MSMS环境查看和修改数据
cmdProducts.CommandText = "select * from products where ProductID=1";
cmdProducts.ExecuteNonQuery();
//下面行下断点,发现仍无法读取数据,因为前面的Update已经锁定记录
throw new Exception("Rollback()");
tranDebug.Commit();
}
catch (SqlException ex)
{
tranDebug.Rollback();
Console.Write(ex.Message);
//throw;
}
finally
{
cnNorthwind.Dispose();
}
}
}