本文主要涉及LinqToSql数据库事务相关,文章不足之处,欢迎您指出。
一、回顾T-SQL中的事务机制:
代码如下:
1
/*
加入事务机制后的存储过程
*/
2
create
procedure
sp_example
3
@param1
int
=
null
,
4
@param2
nvarchar
(
20
)
=
null
5
as
6
begin
tran
tranName
/*
sql 事务的加入
*/
7
insert
into
table0 (col1,col2,col3)
values
(
'
value1
'
,
'
value2
'
,
'
value3
'
)
8
update
table1
set
column1
=
@param1
where
1
=
1
9
--
删除table2中一条已经被其他外键表引用的记录,此时会报sql引用错误
10
delete from
table2
where
column3
=
@param1
11
insert
into
table3 (col1,col2)
values
(
'
value1
'
,
'
value2
'
)
12
if
(
@@error
=
)
13
commit
tran
tranName
14
else
15
rollback
tran
tranName
16
go
以上代码是一个具备事务机制的简单存储过程,需要指出的是当上述代码执行到第十行时,此时如果该存储过程未加入事务机制那么势必会导致第10行之前已经被影响的数据库记录也不会被还原(rollback)。这样的代码是我们不想见到的,所以事务在复杂的商业逻辑中保持数据的完整性还是尤为重要的。
二、LinqToSql 中的SubmitChanges内置事务机制:
众所周知LinqToSql 中我们的事务机制代码变的相对简单了,如以下代码:
1
public
bool
DeleteDepartment(
int
departmentId)
2
{
3
try
4
{
5
DataContext.SystemUser.DeleteOnSubmit(
6
DataContext.SystemUser.FirstOrDefault(u
=>
u.DepartmentID
==
departmentId));
7
8
DataContext.Department.DeleteOnSubmit(
9
DataContext.Department.FirstOrDefault(f
=>
f.DepartmentID
==
departmentId));
10
11
//
事务机制被封装到SubmitChanges方法内
12
DataContext.SubmitChanges();
13
14
return
true
;
15
}
16
catch
17
{
18
return
false
;
19
}
20
}
上述代码很容易理解,在LinqToSql 为了删除一条部门记录。我们首选要删除该部门被引用的外键表记录这里是员工表,(以上代码只是为举例用,实际开发中是不会有此种业务的)当外键记录都删除成功后代码执行到第8行,这时才能能删除部门对象。否则报SqlException外键引用无法删除部门记录。我们唯一需要做的只是将 DataContext.SubmitChanges();这句放在所有Linq操作数据库语句之后这样就可以调用数据库事务机制了。比如当第5行代码执行时SystemUser还被Order表引用。当SubmitChanges执行时会自动调用transaction.Rollback()方法回滚SubmitChanges()之前的所有被影响的数据库记录,详情请阅Reflector。
三、在LinqToSql中SubmitChange内置事务机制无法满足的业务场景:
当程序需要处理更多更复杂的商业逻辑时,我发现光凭SubmitChange方法自带的事务机制是远远不能满足的。
该场景描述如下:
如果为完成某一个特定的业务,需要在程序中使用多次的SubmitChanges方法。比如我们要做一个库存相关业务,该业务是由两张表组成:主表+从表。分别为主表:Depot和从表:DepotDetail 两张表。两张表关系如下:

当我们通过LinqToSql生成一个库存对象时其实应先生成Depot对象后再将生成Depot对象的DepotID(主键)传递到DepotDetail对象中用于生成库存明细表记录。也就说为了生成库存明细表记录我们必须先生成Depot主表,那样就不得不先调用SubmitChanges方法,当保存DepotDetail对象时还需要再一次调用SubmitChanges()方法。因为调用了多次SubmitChanges方法所以SubmitChanges内置的回滚机制已经不能满足需要了。
四、TransactionScope的应用:
我们需要引用.net 的System.Transactions 类库使用TransactionScope类,帮我们更有效的处理数据库事务机制。对TransactionScope进行封装,代码如下:
本段代码原作者,被我稍稍改造如下:
DBTransactionExtension
1
public
static
class
DBTransactionExtension
2
{
3
public
static
bool
Excute(
out
string
errorMsg,
params
Action[] actions)
4
{
5
//
使用ReadCommitted隔离级别,保持与Sql Server的默认隔离级别一致
6
return
Excute(
out
errorMsg, IsolationLevel.ReadCommitted,
null
, actions);
7
8
}
9
10
public
static
void
Excute(
out
string
errorMsg, IsolationLevel level,
params
Action[] actions)
11
{
12
Excute(
out
errorMsg, level,
null
, actions);
13
}
14
15
public
static
void
Excute(
out
string
errorMsg,
int
timeOut,
params
Action[] actions)
16
{
17
Excute(
out
errorMsg, IsolationLevel.ReadCommitted, timeOut, actions);
18
}
19
20
public
static
bool
Excute(
out
string
errorMsg, IsolationLevel level,
int
?
timeOut,
params
Action[] actions)
21
{
22
errorMsg
=
""
;
23
if
(actions
==
null
||
actions.Length
==
)
24
return
false
;
25
TransactionOptions options
=
new
TransactionOptions();
26
27
options.IsolationLevel
=
level;
//
默认为Serializable,这里根据参数来进行调整
28
29
if
(timeOut.HasValue)
30
31
options.Timeout
=
new
TimeSpan(
,
, timeOut.Value);
//
默认60秒
32
33
using
(TransactionScope tran
=
new
TransactionScope(TransactionScopeOption.Required, options))
34
{
35
try
36
{
37
Array.ForEach
<
Action
>
(actions, action
=>
action());
38
tran.Complete();
//
通知事务管理器它可以提交事务
39
return
true
;
40
}
41
catch
(Exception ex)
//
回滚事务
42
{
43
errorMsg
=
ex.Message;
44
return
false
;
45
}
46
}
47
}
48
49
}
调用DBTransactionExtension代码如下:
1
private
void
SaveDepot(Depot depot)
2
{
3
DataContext.Depots.InsertOnSubmit(depot);
4
5
if
(
false
)
//
TODO:保存库存主表前的逻辑判断,条件不满足时候调用 throw new exception执行TransactionScope回滚。
6
throw
new
Exception(
"自定义错误提示内容,最终由事务获取错误信息后抛给UI"
);
7
8
//
条件满足则调用SubmitChanges
9
DataContext.SubmitChanges();
10
DepotDetail depotDetail
=
new
DepotDetail();
11
depotDetail.DepotID
=
depot.DepotID;
12
depotDetail.Count
=
100
;
13
14
DataContext.DepotDetails.InsertOnSubmit(depotDetail);
15
//
又调用了一次SubmitChanges
16
DataContext.SubmitChanges();
17
}
18
public
Depot InvokeTransaction(Depot depot,
out
string
errorMsg)
19
{
20
try
21
{
22
DBTransactionExtension.Excute(
out
errorMsg, ()
=>
SaveDepot(depot));
23
return
depot;
24
}
25
catch
(Exception ex)
26
{
27
errorMsg
=
ex.Message;
28
return
null
;
29
}
30
}
根据上述调用方法,我们已经可以在LinqToSql中灵活的使用数据库事务了。
五、TransactionScope类使用的注意事项:
使用TransactionScope时如果调用多次LinqToSql的DataContext对象实例(等同调用多个数据库连接),那么我们必须开启MSDTC否则事务不能正常工作,具体请阅MSDTC开启。注:TransactionScope 适用于多种 Data Provider 比如 oracle 、OleDB、ODBC等。
最后希望本篇文章能给您带来帮助。
原文链接: http://www.cnblogs.com/ryanding/archive/2010/12/02/1892746.html