首页 > 其他分享 >DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

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

时间:2024-09-30 10:52:25浏览次数:9  
标签:IUnitOfWork return IDbContext Repository TEntity dbContext entity public

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

 

上一篇:《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 属性定义。

标签:IUnitOfWork,return,IDbContext,Repository,TEntity,dbContext,entity,public
From: https://www.cnblogs.com/sexintercourse/p/18441424

相关文章

  • 删除使用add-apt-repository添加的存储库
    我正在安装MicrosoftSQLServerforLinux,我不小心用sudoadd-apt-repository"$(wget-qO-https://packages.microsoft.com/config/ubuntu/18.04/mssql-server-2019.list)"为错误的Linux版本(我想要的是20.04版本)添加了存储库。如何撤消上述行的结果?您可以像这样删除它:sudo......
  • @Mapper,@Repository,@MapperScan注解对比
    1.@Repository@Repository是标注在Dao层接口上的,作用是将接口的一个实现类交给Spring管理。但是它需要配合@MapperScan进行使用,把@MapperScan("Mapper接口层路径")添加到启动类,系统会扫描持久层创建实现类并交给spring管理。2.@Mapper@Mapper也是使用在Dao层接口上的,使用它后就......
  • git报错 fatal: unsafe repository 解决方法 xxx is owned by someone else
    转载来自:https://www.aspirantzhang.com/network/git-fatal-unsafe-repository.htmlgit近期进行了版本升级,添加了新的目录安全限制。造成在进行git常规操作时,或在各类编辑器如VSCode中无法发现.git文件,报错:fatal:unsaferepository(xxxisownedbysomeoneelse.)Toaddan......
  • 解决:The GPG keys listed for the "MySQL 8.0 Community Server" repository are alre
    安装mysql提示Retrievingkeyfromfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysqlTheGPGkeyslistedforthe"MySQL8.0CommunityServer"repositoryarealreadyinstalledbuttheyarenotcorrectforthispackage.CheckthatthecorrectkeyURLsarecon......
  • Centos8下载报错:Errors during downloading metadata for repository ‘appstream‘:
    前提使用docker安装centos8步骤复现:启动后发现环境中没有vim功能,想着安装一下,发现报错:Errorsduringdownloadingmetadataforrepository'appstream':原因:找到AI工具翻译后:结合centos系统在2021年就没有在进行维护了,再进行网上大量查阅得知应该是yum源的问题,这里......
  • Sonatype Nexus Repository搭建与使用(详细教程3.70.1)
    目录一.环境准备二.安装jdk三.搭建Nexus存储库四.使用介绍 一.环境准备主机名IP系统软件版本配置信息nexus192.168.226.26Rocky_linux9.4NexusRepository3.70.1MySQL8.0jdk-11.0.232核2G,磁盘20G进行时间同步,关闭防火墙和selinuxJavaArchiveDownloads......
  • SpringCloud Alibaba - spring cloud gateway InMemoryRouteDefinitionRepository
    InMemoryRouteDefinitionRepository是SpringCloudGateway中用于存储路由定义(RouteDefinition)的一个实现类,它将所有的路由信息保存在内存中。这个类主要用于简单场景或者开发阶段,因为它提供了快速的读写访问能力,但不支持动态刷新或持久化存储。工作原理:初始化:......
  • [email protected]: Permission denied (publickey). fatal: Could not read from remote
    遇到 [email protected]:Permissiondenied(publickey). 这个错误通常意味着你的SSH密钥没有被GitHub识别或配置不正确。这里有几个步骤可以帮助你解决这个问题:1.检查SSH密钥是否已添加到GitHub查看你的SSH密钥:在本地机器上,你可以使用 ssh-keygen-l-Emd5-f~/.ssh/id_r......
  • MongoRepository 操作 AWS DocumentDB时,如何达到与MySql 中有 select … for update
    在MySQL中,SELECT...FORUPDATE用于在事务中对读取的数据行加锁,以防止其他事务同时修改这些行。这种行级锁定机制在关系型数据库中广泛使用,以确保数据一致性。在MongoDB或AWSDocumentDB中,类似的效果可以通过以下方式实现:使用FindandModify操作:MongoDB提供了f......
  • detected dubious ownership in repository 问题彻底解决大全
    fatal:detecteddubiousownershipinrepositoryat'C:\lindexi\Code\Foo'isownedby:'S-1-5-21-469934170-xxx-xxx-1001'butthecurrentuseris:'S-1-5-21-469994170-aaa-bbb-1001' 这个问题给我很大困扰,我的电脑因为强制改用户名造成了大量bug,无法删除用户安全......