谈谈 Repository、IUnitOfWork 和 IDbContext 的实践

上一篇:《DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)

这篇文章主要是对 DDD.Sample 框架增加 Transaction 事务操作,以及增加了一些必要项目。

虽然现在的 IUnitOfWork 实现中有 Commit 的实现,但也就是使用的 EF SaveChanges,满足一些简单操作可以,但一些稍微复杂点的实体操作就不行了,并且 Rollback 也没有实现。

现在的 UnitOfWork 实现代码:

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 事务操作。

如果单独在 EF 中实现 Transaction 操作,可以使用 TransactionScope,参考文章:在 Entity Framework 中使用事务

TransactionScope 的实现比较简单,如何在 DDD.Sample 框架中,结合 IUnitOfWork 和 IDbContext 进行使用呢?可能实现方式有很多中,现在我的实现是这样:

首先,IDbContext 中增加 Database 属性定义:

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 增加和改动了一些东西:

  • 增加 DDD.Sample.Domain.DomainEvents、DDD.Sample.Domain.DomainServices 和 DDD.Sample.Domain.ValueObjects。
  • 从 DDD.Sample.Domain 分离出 DDD.Sample.Domain.Repository.Interfaces。
  • 增加 DDD.Sample.BootStrapper,执行 Startup.Configure 用于系统启动的配置。
  • 去除 IEntity,在 IAggregateRoot 中添加 Id 属性定义。

谈谈 Repository、IUnitOfWork 和 IDbContext 的实践的更多相关文章

  1. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...

  2. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...

  3. Repository、IUnitOfWork 和 IDbContext 的实践

    Repository.IUnitOfWork 和 IDbContext 的实践 好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 ...

  4. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)

    好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 DDD 架构设计的一些东西运用在上面,但发现了很多问题,这些在之前的短消息项目中 ...

  5. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)

    http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...

  6. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践

    http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html

  7. Repository、IUnitOfWork和IDbContext

    DDD 领域驱动设计-谈谈Repository.IUnitOfWork和IDbContext的实践 上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDb ...

  8. Repository、IUnitOfWork 和 IDbContext

    1)领域层不应该直接依赖于仓储实现:如果领域层依赖于仓储实现,一是技术绑定太紧密,二是仓储要对领域对象作操作,会造成循环依赖. 2)将接口定义在领域层,减少技术架构依赖,应用层或领域层要使用某个仓储实 ...

  9. 简单谈谈DNS的工作原理及实践

    DNS协议简介 dns(Domain Name System)是一个全球化的分布式数据库系统,用于存储域名和互联网IP地址的映射关系.dns协议是计算机协议栈应用层中,应用最广泛的协议之一.用户每一次 ...

随机推荐

  1. .NET 命名规范 代码示例

    class Person { /// <summary> /// 公有字段.属性 首字母大写 /// </summary> public string FirstName; p ...

  2. SSDT Hook

    一.效果图 二.分析 这里对NtCreateProcessEx做拦截,用WinDbg来定位该函数在SSDT中的记录地址: : kd> dd KeServiceDescriptorTable 80 ...

  3. Blend制作的下载动画

    最近使用Blend制作了一个下载动画,感觉很有意思,所以这篇给各位介绍下如何使用Blend制作一个简单的下载动画的步骤 首先拖出一个圆,参数如下: 选中椭圆后单击Properties面板,选择“Fil ...

  4. jMeter 监控cpu、内存

    http://jmeter-plugins.org/downloads/all/ 将JMeterPlugins.jar包复制到Jmeter的lib目录下面的ext目录下面,重新启动Jmeter,我们点 ...

  5. 如何在linux中配置PHP环境

    yum -y install httpd php mysql mysql-server php-mysql//安装mysql扩展yum -y install mysql-connector-odbc( ...

  6. Apache 多端口多站点配置实例

    分享下Apache多端口多站点的配置方法,配置apache服务器的朋友参考下. 配置httpd.conf 监听多个端口 复制代码代码如下: # Listen: Allows you to bind A ...

  7. Yii整合ZF2及soap实例

    一)如何整合? // change the following paths if necessary $yii = dirname(__FILE__).'/framework/yii.php'; $c ...

  8. ViewPager中GridView问题

    GridView 嵌套在ViewPager中问题. 1. GridView属性设置无法显示. 正常显示方式 <GridView android:padding="8dip" ...

  9. OXPattern

    10000的随机数组由ox组成,查找数组中oox...x(任意x)oox....x(任意x)o的个数 enum { DATA_SIZE = , }; enum enum_status { STATUS ...

  10. 小米miui5系统的webview在处理动画事件transitionEnd事件时,竟然要用transitionend才行

    一般的安卓系统用的是webkitTransitionEnd, 而小米的系统我用了webkitTransitionEnd事件无法执行,只能用transitionend才会被执行,怪