上一篇:《DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)》html
这篇文章主要是对 DDD.Sample 框架增长 Transaction 事务操做,以及增长了一些必要项目。git
虽然如今的 IUnitOfWork 实现中有 Commit 的实现,但也就是使用的 EF SaveChanges,知足一些简单操做能够,但一些稍微复杂点的实体操做就不行了,而且 Rollback 也没有实现。github
如今的 UnitOfWork 实现代码:web
public class UnitOfWork : IUnitOfWork { private IDbContext _dbContext; public UnitOfWork(IDbContext dbContext) { _dbContext = dbContext; } public void RegisterNew<TEntity>(TEntity entity) where TEntity : class { _dbContext.Set<TEntity>().Add(entity); } public void RegisterDirty<TEntity>(TEntity entity) where TEntity : class { _dbContext.Entry<TEntity>(entity).State = EntityState.Modified; } public void RegisterClean<TEntity>(TEntity entity) where TEntity : class { _dbContext.Entry<TEntity>(entity).State = EntityState.Unchanged; } public void RegisterDeleted<TEntity>(TEntity entity) where TEntity : class { _dbContext.Set<TEntity>().Remove(entity); } public async Task<bool> CommitAsync() { return await _dbContext.SaveChangesAsync() > 0; } public void Rollback() { throw new NotImplementedException(); } }
基于上面的实现,好比要处理这样的一个操做:先添加一个 Teacher,而后再添加一个 Student,Student 实体中有一个 TeacherId,通常实现方式以下:数据库
public async Task<bool> Add(string name) { var teacher = new Teacher { Name = "teacher one" }; _unitOfWork.RegisterNew(teacher); await _unitOfWork.CommitAsync(); //可能还有一些 web 请求操做,好比 httpClient.Post(tearch); 可能会出选异常。 var student = new Student { Name = name, TeacherId = teacher.Id }; _unitOfWork.RegisterNew(student); await _unitOfWork.CommitAsync(); return true; }
上面的实现可能会出现一些问题,好比添加 Teacher 出现了异常,web 请求出现了异常,添加 Student 出现了异常等,该如何进行处理?因此你可能会增长不少判断,还有就是异常出现后的修复操做,当需求很复杂的时候,咱们基于上面的处理也就会更加复杂,根本缘由是并无真正的实现 Transaction 事务操做。app
若是单独在 EF 中实现 Transaction 操做,可使用 TransactionScope,参考文章:在 Entity Framework 中使用事务框架
TransactionScope 的实现比较简单,如何在 DDD.Sample 框架中,结合 IUnitOfWork 和 IDbContext 进行使用呢?可能实现方式有不少中,如今个人实现是这样:异步
首先,IDbContext 中增长 Database 属性定义:async
public interface IDbContext { Database Database { get; } //add DbSet<TEntity> Set<TEntity>() where TEntity : class; DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class; Task<int> SaveChangesAsync(); }
增长 Database 属性的目的是,便于咱们在 UnitOfWork 中访问到 Transaction,其实还能够定义这样的接口:DbContextTransaction BeginTransaction();
,但 EF 中的 DbContext 并无进行实现,而是须要经过 Database 属性,因此还须要在 IDbContext 实现中额外实现,另外增长 Database 属性的好处,还有就是能够在 UnitOfWork 中访问执行不少的操做,好比执行 SQL 语句等等。函数
这里须要说下 IDbContext 定义,我原先的设计初衷是,让它脱离 EF,做为全部数据操做上下文的定义,但其实实现的时候,仍是脱离不了 EF,由于接口返回的类型都在 EF 下,最后 IDbContext 就变成了 EF DbContext 的部分接口定义,因此这部分是须要再进行设计的,但好在有了 IDbContext,可让 EF 和 UnitOfWork 隔离开来。
SchoolDbContext 中的实现没有任何变换,由于继承的 EF DbContext 已经有了实现,UnitOfWork 改动比较大,代码以下:
public class UnitOfWork : IUnitOfWork { private IDbContext _dbContext; private DbContextTransaction _dbTransaction; public UnitOfWork(IDbContext dbContext) { _dbContext = dbContext; _dbTransaction = _dbContext.Database.BeginTransaction(); } public async Task<bool> RegisterNew<TEntity>(TEntity entity) where TEntity : class { _dbContext.Set<TEntity>().Add(entity); return await _dbContext.SaveChangesAsync() > 0; } public async Task<bool> RegisterDirty<TEntity>(TEntity entity) where TEntity : class { _dbContext.Entry<TEntity>(entity).State = EntityState.Modified; return await _dbContext.SaveChangesAsync() > 0; } public async Task<bool> RegisterClean<TEntity>(TEntity entity) where TEntity : class { _dbContext.Entry<TEntity>(entity).State = EntityState.Unchanged; return await _dbContext.SaveChangesAsync() > 0; } public async Task<bool> RegisterDeleted<TEntity>(TEntity entity) where TEntity : class { _dbContext.Set<TEntity>().Remove(entity); return await _dbContext.SaveChangesAsync() > 0; } public void Commit() { _dbTransaction.Commit(); } public void Rollback() { _dbTransaction.Rollback(); } }
UnitOfWork 构造函数中,根据 DbContext 建立 DbContextTransaction 对象,而后在实体每一个操做中,都添加了 SaveChanges,由于咱们用了 Transaction,因此在执行 SaveChanges 的时候,并无应用到数据库,但能够获取到新添加实体的 Id,好比上面示例 Student 中的 TeacherId,而且用 Sql Profiler 能够检测到执行的 SQL 代码,当执行 Commit 的时候,数据对应进行更新。
测试代码:
public async Task<bool> AddWithTransaction(string name) { var teacher = new Teacher { Name = "teacher one" }; await _unitOfWork.RegisterNew(teacher); //可能还有一些 web 请求操做,好比 httpClient.Post(tearch); 可能会出选异常。 var student = new Student { Name = name, TeacherId = teacher.Id }; await _unitOfWork.RegisterNew(student); _unitOfWork.Commit(); return true; }
在上面代码中,首先在没执行到 Commit 以前,是能够获取到新添加 Teacher 的 Id,而且若是出现了任何异常,都是能够进行回滚的,固然也能够手动进行 catch 异常,并执行_unitOfWork.Rollback()
。
不过上面的实现有一个问题,就是每次实体操做,都用了 Transaction,性能我没测试,但确定会有影响,好处就是 IUnitOfWork 基本没有改动,仍是按照官方的定义,只不过部分接口改为了异步接口。
除了上面的实现,还有一种解决方式,就是在 IUnitOfWork 中增长一个相似 BeginTransaction 的接口,大体实现代码:
public class UnitOfWork : IUnitOfWork { private IDbContext _dbContext; private DbContextTransaction _dbTransaction; public UnitOfWork(IDbContext dbContext) { _dbContext = dbContext; } //add public void BeginTransaction() { _dbTransaction = _dbContext.Database.BeginTransaction(); } public async Task<bool> RegisterNew<TEntity>(TEntity entity) where TEntity : class { _dbContext.Set<TEntity>().Add(entity); if (_dbTransaction != null) return await _dbContext.SaveChangesAsync() > 0; return true; } public async Task<bool> RegisterDirty<TEntity>(TEntity entity) where TEntity : class { _dbContext.Entry<TEntity>(entity).State = EntityState.Modified; if (_dbTransaction != null) return await _dbContext.SaveChangesAsync() > 0; return true; } public async Task<bool> RegisterClean<TEntity>(TEntity entity) where TEntity : class { _dbContext.Entry<TEntity>(entity).State = EntityState.Unchanged; if (_dbTransaction != null) return await _dbContext.SaveChangesAsync() > 0; return true; } public async Task<bool> RegisterDeleted<TEntity>(TEntity entity) where TEntity : class { _dbContext.Set<TEntity>().Remove(entity); if (_dbTransaction != null) return await _dbContext.SaveChangesAsync() > 0; return true; } public async Task<bool> Commit() { if (_dbTransaction == null) return await _dbContext.SaveChangesAsync() > 0; else _dbTransaction.Commit(); return true; } public void Rollback() { if (_dbTransaction != null) _dbTransaction.Rollback(); } }
上面这种实现方式就解决了第一种方式的问题,须要使用 Transaction 的时候,直接在操做以前调用 BeginTransaction 就好了,但很差的地方就是改动了 IUnitOfWork 的接口定义。
除了上面两种实现方式,你们若是有更好的解决方案,欢迎提出。
另外,DDD.Sample 增长和改动了一些东西: