上个月,同事问我 DAL 里的 CUD 方法若需要共用,事务要怎么写?
首先,要思考处理数据库的 Data Access Layer 里的 CUD方法,该不该共用?
- 以使用者案例的角度切入看 DAL,它不应该有机会共用,因为每一个作业流程的数据异动方式不会一样。
- 设计 DAL 方法时,不应该用 Table 的 CRUD 作为 DAO 的 Method,相信我,那只会让事情变得更复杂而已。
- 共用 DAL 方法,就得把检查机制放在 DAL 方法。
我的 DAL 是依作业流程设计,比如请假作业,就会对应到请假作业的 DAL,该 DAL 就会提供请假作业的查询、新增、更新、删除等功能
架构如下图:
万一,你检视使用者案例后,发现真的要共用方法,那该怎么办?
开始之前:
- 在 DAL 里每一个方法里的 DbConnection 应该是独立的私有等级,用完就立即放掉,如果你的 DbConnection 是属于全域变量等级,就要用解构子关闭对象;若是属于静态全域等级,我只能为你念佛超渡。
- 以下的范例,是使用 ADO.NET Entity Framework 的 DbContext,这跟 ADO.NET 的 DbConnection 是一样的意思
先来看看不好的写法:
在同一个数据库连线里,使用TransactionScope是最无脑,也是最耗资源的,处理不好就会有一大堆的 lock 等着你去处理
public int FLowA()
{
var result = -1;
using (var db = new FlowDbContext())//←处理数据用完就关掉
{
//.....TODO:处理数据
result = db.SaveChanges();//←保存数据,db.SaveChanges 具有事务的功能
}
return result;
}
FlowB 跟 A一样
当需要合并两个 Flow 最无脑的做法就是用 TransactionScope 包起来
public int CombineFlowAB()
{
var result = -1;
using (var scope = new TransactionScope())//←不好的写法,不要学
{
var resultA = FLowA();
var resultB = FLowB();
result = resultA + resultB;
scope.Complete();
}
return result;
}
改善方法:
首先,把连线对象抽出来变成参数,让 isDispose 决定是否要调用 DbContext.Dispose 方法
public int FLowA(FlowDbContext db = null)//← 首先,把连线对象抽出来变成参数,我要由外面决定事务、关闭
{
var result = -1;
var isDispose = false;
if (db == null)//← 若外面没有传东西进来,我就自己建立连线,并开启 isDispose 旗标
{
db = new FlowDbContext();
isDispose = true;
}
try
{
//.....TODO:处理数据
result = db.SaveChanges();//保存数据
return result;
}
finally
{
if (isDispose)//← 自己挖的洞自己跳
{
db.Dispose();
}
}
}
由外部方法调用 DbContext.Database.BeginTransaction
public int CombineFlowAB()
{
var result = -1;
using (var db = new FlowDbContext())
using (var trans = db.Database.BeginTransaction())←声明事务对象,此时SaveChanges就不具有事务机制
{
try
{
var resultA = FLowA(db);
var resultB = FLowB(db);
result = resultA + resultB;
trans.Commit();
return result;
}
catch (Exception)
{
trans.Rollback();
throw;
}
}
}
这样一来 FlowA、B 能拥有自己的事务权杖,也能适时地将事务权杖释放出去
若有谬误,烦请告知,新手发帖请多包涵
2010~2017 C# 第四季
原文:大专栏 [C#.NET][Entity Framework] 实践 DAL 共用方法的交易