首页 > 其他分享 >Generic Repository&UnitOfWork基本实现

Generic Repository&UnitOfWork基本实现

时间:2023-12-29 15:45:21浏览次数:27  
标签:IUnitOfWork 事务 Repository Generic public UnitOfWork var new order

前言

在DbContext中已经具备了事务,对于多个实体的操作,能够在一个事务中保证。借助仓储在基于DbContext上的封装,我们能够更好的扩展复用。泛型仓储的使用又能简化对于基础功能的依赖,但是当现有事务范围不足以覆盖或是多个仓储操作,多次调用SaveChange后,整体的事务范围便发生了变化,不能形成一个整体了。


事务范围

单次SaveChange

图片

新增Order及OrderItem,两者本身都没问题的话,一次SaveChange可以全部成功。而当SaveChange完毕后,再出现异常(如下直接抛出异常test),已有的事务已经提交完毕,无法回滚之前的操作了。

[HttpPost]
public async Task<int> CreateAsync(string name)
{
    var order = new Order()
    {
        Name = name,
        CreateDate = DateTime.UtcNow,
        OrderItems = new List<OrderItem>()
        {
            new OrderItem()
            {
                ProductId = 1,
                Amount = 1,
            }
        }
    };
    order = await _orderRepository.InsertAsync(order, autoSave: true);
    //throw new Exception("test");

    return order.Id;
}

多次SaveChange

类似于直接抛出事务,实现多次调用SaveChange,

图片

第一次执行正常,执行SaveChange后数据入库,但在第二次操作时出现失败,第二次的入库操作能够回滚,但是第一次的SaveChange却是还是执行成功,从整个操作上,缺失了一致性。

public async Task<int> CreateMultiAsync(string name)
{
    var order = new Order()
    {
        Name = name,
        CreateDate = DateTime.UtcNow,
        OrderItems = new List<OrderItem>()
        {
            new OrderItem()
            {
                ProductId = 1,
                Amount = 1,
            }
        }
    };
    order = await _orderRepository.InsertAsync(order, autoSave: true);

    var orderError = new Order()
    {
        Name = name,
        CreateDate = DateTime.Now,//数据库中限制只允许utc
        OrderItems = new List<OrderItem>()
        {
            new OrderItem()
            {
                ProductId = 1,
                Amount = 1,
            }
        }
    };
    orderError = await _orderRepository.InsertAsync(orderError, autoSave: true);

    return order.Id;
}

这种场景在结合领域事件,在handler中还执行一些仓储操作时很容易出现,当没有一个全局的事务保证下,不可避免有这种风险。


全局事务

事务延伸

EFCore事务机制中,可以通过DbContext开启一个事务,然后将所有数据库操作纳入其中。对于单次或是多次SaveChange,便可以合并成一个事务,形成一个整体。

图片

第一次执行SaveChange操作,虽然插入到库,但并未实际提交,不会在表中出现。而当第二次SaveChange如成功,则执行Commit, 两个操作顺利入库。如果发生异常,则第一次操作便是被回滚。

public async Task<int> CreateMultiAsync(string name)
{
    var dbContext = await _orderRepository.GetDbContextAsync();

    using (var transition = dbContext.Database.BeginTransaction())
    {
        var order = new Order()
        {
            Name = name,
            CreateDate = DateTime.UtcNow,
            OrderItems = new List<OrderItem>()
            {
                new OrderItem()
                {
                    ProductId = 1,
                    Amount = 1,
                }
            }
        };
        order = await _orderRepository.InsertAsync(order, autoSave: true);

        var orderError = new Order()
        {
            Name = name,
            CreateDate = DateTime.Now,//数据库中限制只允许utc
            OrderItems = new List<OrderItem>()
            {
                new OrderItem()
                {
                    ProductId = 1,
                    Amount = 1,
                }
            }
        };
        orderError = await _orderRepository.InsertAsync(orderError, autoSave: true);
        transition.Commit();

        return order.Id;
    }
}

如此一来,事务的范围便延伸起来,但麻烦总是接踵而至,想要一个统一的机制来写事务的开启,提交,而不像如上一样,需要时候写一节代码;并且当多个服务协作时,代码上还是无法形成一个整体的事务,总会充斥着各种嵌套的、平级的调用顺序。

ServiceX.Create()
{
  using (var transition = dbContext.Database.BeginTransaction())
  {
    // do something
    ServiceA.Create();
    ServiceB.Create();
    transition.Commit();
  }
}

ServiceA.Create()
{
  using (var transition = dbContext.Database.BeginTransaction())
  {
    // do something
    transition.Commit();
  }
}

ServiceB.Create()
{
  using (var transition = dbContext.Database.BeginTransaction())
  {
    // do something
    transition.Commit();
  }
}

工作单元

每次从仓储中取得DbContext开启事务,稍显麻烦,封装一层,加入工作单元概念,由它来管理事务的开启与提交。

Unit Of Work:维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决。即管理对象的CRUD操作,以及相应的事务与并发问题等。是用来解决领域模型存储和变更工作,而这些数据层业务并不属于领域模型本身具有的。
从其定义来看,DbContext本身便已是实现了Uow,只是当我们需要将其扩展到一个业务用例时,需要协调多个仓储,多个聚合根等,其工作单元的范围也不再局限于DbContext的一次操作了,而是要扩展到整个用例的范围。

IUnitOfWork定义

首先将dbContext的获取与事务提交的职责移交到IUnitOfWork中,定义如下抽象接口

public interface IUnitOfWork : IDisposable
{
    IDbContextTransaction BeginTransaction();
    void Commit();
}c

借助于依赖注入与DbContext的生命周期,UnitOfWork和Repository能够共用DbContext,如此依赖,代码能够简化

_unitOfWork.BeginTransaction();

var order = new Order();
order = await _orderRepository.InsertAsync(order, autoSave: true);

_unitOfWork.Commit();

再进一步考虑嵌套事务,如果被嵌套的事务中提前进行了Commit,那么导致后续的操作则会报错,因此被嵌套的事务要么不能写,要么要有方法使其知道是被嵌套中,然后令其失效。

ServiceX.Create()
{
  _unitOfWorkOuter.BeginTransaction();
  // do something
   ServiceA.Create();
   ServiceB.Create();
  _unitOfWorkOuter.Commit();
}

ServiceA.Create
{
  _unitOfWorkInner.BeginTransaction();
  // do something
  _unitOfWorkInner.BeginTransaction();
}

ServiceB.Create
{
  _unitOfWorkInner.BeginTransaction();
  // do something
  _unitOfWorkInner.BeginTransaction();
}

IUnitOfWorkManager定义

为了使其能够区分被嵌套的事务,需要能够在外层事务开启后,再注入内层事务时,能够外层事务已经存在,或许可以再包一层,来保存外层事务,增加一个IUnitOfWorkManager(参照ABP中代码实现)。

public interface IUnitOfWorkManager
{
    IUnitOfWork Current { get; }
    IUnitOfWork Begin();
}

为了实现内层事务,新增IUnitOfWorkInner,增加空实现

public class UnitOfWorkInner : IUnitOfWork
{
    private readonly IUnitOfWork _unitOfWorkOuter;

    public UnitOfWorkInner(IUnitOfWork unitOfWorkOuter)
    {
        _unitOfWorkOuter = unitOfWorkOuter;
    }

    public IDbContextTransaction BeginTransaction()
    {
        return _unitOfWorkOuter.BeginTransaction();
    }

    public void Commit()
    {
    }

    public void Dispose()
    {
    }
}

在UnitOfWorkManager中,根据是否存在外层事务来决定内存事务的实例。

public class UnitOfWorkManager : IUnitOfWorkManager
{
    private readonly IUnitOfWork _unitOfWork;

    public UnitOfWorkManager(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public IUnitOfWork Begin()
    {
        var outerUow = Current;

        if (outerUow != null)
        {
            return new UnitOfWorkInner(outerUow);
        }

        var uow = _unitOfWork;
        uow.BeginTransaction();

        Current = uow;

        return uow;
    }

    public IUnitOfWork Current
    {
        get { return GetCurrentUow(); }
        set { SetCurrentUow(value); }
    }
    private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>();
    private static IUnitOfWork GetCurrentUow()
    {
        var uow = AsyncLocalUow.Value?.UnitOfWork;
        if (uow == null)
        {
            return null;
        }

        return uow;
    }
    private static void SetCurrentUow(IUnitOfWork value)
    {
        lock (AsyncLocalUow)
        {
            if (AsyncLocalUow.Value?.UnitOfWork == null)
            {
                if (AsyncLocalUow.Value != null)
                {
                    AsyncLocalUow.Value.UnitOfWork = value;
                }

                AsyncLocalUow.Value = new LocalUowWrapper(value);
                return;
            }

            AsyncLocalUow.Value.UnitOfWork = value;
        }
    }
    private class LocalUowWrapper
    {
        public IUnitOfWork UnitOfWork { get; set; }

        public LocalUowWrapper(IUnitOfWork unitOfWork)
        {
            UnitOfWork = unitOfWork;
        }
    }
}

当再次使用到嵌套事务时候,便可以多次Begin得到外层事务与内层事务,内层事务不具备实际提交功能。

var unitOfWorkOuter = _unitOfWorkManager.Begin();
var order = new Order();
order = await _orderRepository.InsertAsync(order, autoSave: true);

var unitOfWorkInner = _unitOfWorkManager.Begin();
var orderLog = new OrderLog();
await _orderlOGRepository.InsertAsync(orderLog, autoSave: true);
unitOfWorkInner.Commit();

unitOfWorkOuter.Commit();

尽管能够解决嵌套事务,然而还能够简化一些工作量,在DDD中,应用层有承担着事务的开启提交职责,借助于AOP,可以将该部分职责在请求用例时自动启动,也就减少了手动unitOfWorkManager的使用。又或是,Get请求不需要开启事务,可以在AOP中判断Get请求时,不开启事务。


参考资料


2023-12-29,望技术有成后能回来看见自己的脚步。

标签:IUnitOfWork,事务,Repository,Generic,public,UnitOfWork,var,new,order
From: https://www.cnblogs.com/CKExp/p/17935007.html

相关文章

  • ubuntu 18.04.6编译uboot提示error: bad value (‘generic-armv7-a’) for ‘-mtune=
    按照按照  (https://rocketboards.org/foswiki/Documentation/EmbeddedLinuxBeginnerSGuide)制作了一个image当编译uboot的时候,发送命令make:makesocfpga_cyclone5_configmake 得到提示:cc1:error:badvalue(‘generic-armv7-a’)for‘-mtune=’switchcc1:note:va......
  • sap.suite.ui.generic.template.ListReport.extensionAPI.ExtensionAPI 的使用场合介
    首先让我们了解一下什么是sap.suite.ui.generic.template.ListReport.extensionAPI.ExtensionAPI。这是一个在SAPFioriElements中用于扩展ListReport应用的API。SAPFioriElements旨在提供一种简洁,高效且一致的用户体验,而不需要开发人员编写大量的前端代码。然而,有些情......
  • sap.suite.ui.generic.template.ListReport.extensionAPI.ExtensionAPI 的使用场合介
    首先让我们了解一下什么是sap.suite.ui.generic.template.ListReport.extensionAPI.ExtensionAPI。这是一个在SAPFioriElements中用于扩展ListReport应用的API。SAPFioriElements旨在提供一种简洁,高效且一致的用户体验,而不需要开发人员编写大量的前端代码。然而,有些情......
  • 本地 SAP UI5 应用部署到远端 ABAP 系统,幕后英雄 ABAP_REPOSITORY_SRV
    SAPODataService是一种基于HTTP的数据访问协议,它支持全功能的CRUD操作(创建、读取、更新和删除),并且支持查询和导航。OData协议的主要优势是其基于标准的HTTP协议,并且使用标准的HTTP动词,如GET、POST、PUT、DELETE等进行数据操作。这意味着任何支持HTTP的平台或设备......
  • SAP 标准 OData 服务 ABAP_REPOSITORY_SRV 的作用介绍
    "SAP标准OData服务/sap/opu/odata/UI5/ABAP_REPOSITORY_SRV是SAPNetWeaverGateway框架提供的一个重要服务,用于与ABAP(AdvancedBusinessApplicationProgramming)仓库进行交互。该服务的作用涵盖了许多关键方面,包括ABAP仓库对象(如程序、函数模块、数据元素等)的检索和管理。通过该......
  • Generic Repository基本实现
    前言自定义仓储能够很大程度方便我们实现功能,但是对于自定义仓储中的公共部分,又是非常基础的功能,如基础增删改和列表查询,分页查询,单个查询等,对于大部分自定义仓储来讲都能够用的上,如果每个自定义仓储中都实现一套,代码冗余度太高,无效工作过滤耗费时间。构建泛型仓储泛型仓储......
  • Java泛型Generics​入门详解
    Java泛型Generics泛型基础知识泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。泛型的格式:<数据类型>注意:泛型只能支持引用数据类型。如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型,此时可以往集合中添加任意的数据类型。带来一个坏处是由于......
  • Spring中@Mapper和@Repository的区别与使用
    本文根据文章:https://blog.csdn.net/m0_45210394/article/details/126223145进行修改@Mapper是mybatis的注解,标注在dao层接口上,可以通过动态代理生成接口的实例bean(编译之后能生成相应的接口实现类)@Repository是spring的注解,也标注在dao层接口上,但是@Repository注解只是表明这......
  • TortoiseGit拉取出现“Could not open repository. libgit2 returned: repository pat
    TortoiseGit拉取出现“Couldnotopenrepository.libgit2returned:repositorypath……”错误的解决办法1、......
  • 【低功耗蓝牙BLE-2】Generic Access Profile(GAP)协议
    原文链接:https://zhuanlan.zhihu.com/p/527434096?utm_id=0 GAP简介通用访问配置文件(GAP)规定了设备在较低级别如何执行控制程序,如设备发现、连接、安全建立等,以确保互操作性,并允许来自不同供应商设备之间的通信。主要的操作有:发现并与配对广播数据建立安全连接GAP以一......