谈谈 Repository、IUnitOfWork 和 IDbContext 的实践

谈谈 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 属性定义。
时间: 2024-10-11 19:07:22

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

Repository、IUnitOfWork和IDbContext

DDD 领域驱动设计-谈谈Repository.IUnitOfWork和IDbContext的实践 上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Repository IUnitOfWork 和 Application Service 的变化 总结三种设计方案 简单总结上篇所做的两个改进: 从 Repository 和 UnitOfWork 中抽离出 IDbCont

Repository、IUnitOfWork

Repository.IUnitOfWork 在领域层和应用服务层之间的代码分布与实现 本来早就准备总结一下关于Repository.IUnitOfWork之间的联系以及在各层中的分布,直到看到田园里的蟋蟀发表的文章:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践>,才觉得有必要发表一下我个人的观点及其相关的实现代码,当然我的观点不一定就比他们的好,我只是表达个人观点而矣,大家勿喷. 关于Repository可以看看DUDU的这篇文

关于Repository、IUnitOfWork 在领域层和应用服务层之间的代码分布与实现

本来早就准备总结一下关于Repository.IUnitOfWork之间的联系以及在各层中的分布,直到看到田园里的蟋蟀发表的文章:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践>,才觉得有必要发表一下我个人的观点及其相关的实现代码,当然我的观点不一定就比他们的好,我只是表达个人观点而矣,大家勿喷. 关于Repository可以看看DUDU的这篇文章:关于Repository模式,我结合实际应用总结其核心概念为:Repository是

ASP.NET Core 中的 ORM 之 Dapper

目录 Dapper 简介 使用 Dapper 使用 Dapper Contrib 或其他扩展 引入工作单元 Unit of Work 源代码 参考 Dapper 简介 Dapper是.NET的一款轻量级ORM工具(GitHub),也可称为简单对象映射器.在速度方面拥有微型ORM之王的称号.它是半自动的,也就是说实体类和SQL语句都要自己写,但它提供自动对象映射.是通过对IDbConnection接口的扩展来操作数据库的. 优点: 轻量,只有一个文件 性能高,Dapper的速度接近与IDataRe

前端代码9种标准最佳实践:CSS

前端工程师对写标准的前端代码的重视程度很高.这些最佳标准实践并不是那个权威组织发布的,而是由大量的前端工程师们在实践过程中的经验总结,目的在于提高代码的可读性,可维护性和性能.那么接着上一篇,我们再来谈谈CSS代码的一些标准实践. 1,命名 和其他语言规范一样,css的命名也讲究命名要有意义,命名要尽可能短但是要足够表达含义:命名的词用连字符连接. 不规范的命名: 1 #navigation{ 2 } 3 .demoimage{ 4 } 5 .error_status{ 6 } 规范的命名: 1

基于Dapper的泛型Repository

为减少代码量,这里实现一个基于Dapper的泛型Repository. 这里需要引用Dapper.dll和Dapper.Contrib.dll. 接口定义: /// <summary> /// Repository接口 /// </summary> /// <typeparam name="T"></typeparam> public interface IRepository<T> where T : class, IEnt

Nhibernate基础使用教程以及简易封装

1.Nhibernate简介 NHibernate是一个面向.NET环境的对象/关系数据库映射工具.对象/关系数据库映射(object/relational mapping,ORM)这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去 简单的说就是将数据库的结构直接映射到实体模型之上,从而简化SQL的数据处理时间.通过XML(Fluent亦可)进行定义数据模型的持久化,nhibernate 内部的映射结构以及如图1-1所示: 2.准备 Nhibernate类库,下

【开源】OSharp框架解说系列(4):架构分层及IoC

〇.前言 前面构造了一个后台管理的界面布局,下面开始讲解整个项目的分层设计. 关于分层,网上已经存在相当多的讨论了,这也是一个程序员初学架构设计最先会碰到的问题. 该不该分层? 怎样分层? 层与层之间是否需要解耦?是否需要设计接口?接口是否是多余的? 看完OSharp的分层设计,我想,你应该多少能得到一些启示. 注:OSharp 开发框架的前身是<MVC实体架构设计>系列中讲到的那个架构示例,所以有很多知识点那个系列讲到了,就不会在这个系列再重复了,如果有什么觉得不太明白的可以参考<MV

20145326蔡馨熤《信息安全系统设计基础》期末总结

20145326蔡馨熤<信息安全系统设计基础>期末总结 每周作业链接汇总 第0周作业 简要内容:初步翻阅课本,提出问题:学会如何使用虚拟机VirtualBox并成功安装Ubantu:预习了Linux基础入门:阅读了老师推荐的博客并写出了自己的感想. 二维码: 第1周作业 简要内容:初步了解Linux操作系统:介绍了核心的Linux命令:find/locate/cheat/grep/which/whereis. 二维码: 第2周作业 简要内容:介绍了Linux系统下的开发环境.vi的基本操作.g