草庐IT

.NetCore基于SqlSugar的工作单元—UnitOfWork

youngq 2023-03-28 原文

.NetCore基于SqlSugar和Aop的工作单元模式(UnitOfWork)实现

Unit Of Work 是什么

Unit Of Work模式,即工作单元,它是一种数据访问模式。它是用来维护一个由已经被业务修改(如增加、删除和更新等)的业务对象组成的列表。它负责协调这些业务对象的持久化工作及并发问题。通过数据库事务Unit Of Work模式会记录所有对象模型修改过的信息,在提交的时候,一次性修改,并把结果同步到数据库。 这个过程通常被封装在事务中。所以在采用Unit Of Work模式好处就在于能够确保数据的完整性,如果在持有一系列业务对象(同属于一个事务)的过程中出现问题,就可以将所有的修改回滚,以确保数据始终处于有效状态,不会出现脏数据。

用一句话概括就是:它让一个接口内的所有增删改操作都在一个事务中,要么同时成功提交,要么同时失败回滚。

代码实现

创建工作单元依赖接口:

/// <summary>
/// 工作单元依赖接口
/// </summary>
public interface IUnitOfWork
{
    /// <summary>
    /// 开启工作单元处理
    /// </summary>
    /// <param name="context"></param>
    /// <param name="unitOfWork"></param>
    void BeginTransaction(ActionExecutingContext context);

    /// <summary>
    /// 提交工作单元处理
    /// </summary>
    /// <param name="resultContext"></param>
    /// <param name="unitOfWork"></param>
    void CommitTransaction(ActionExecutedContext resultContext);

    /// <summary>
    /// 回滚工作单元处理
    /// </summary>
    /// <param name="resultContext"></param>
    /// <param name="unitOfWork"></param>
    void RollbackTransaction(ActionExecutedContext resultContext);

    /// <summary>
    /// 执行完毕(无论成功失败)
    /// </summary>
    /// <param name="context"></param>
    /// <param name="resultContext"></param>
    void OnCompleted(ActionExecutingContext context, ActionExecutedContext resultContext);
}

继承接口并实现:

public class SqlSugarUnitOfWork : IUnitOfWork
{
    private readonly ISqlSugarClient _sqlSugarClient;

    public SqlSugarUnitOfWork(ISqlSugarClient sqlSugarClient)
    {
        this._sqlSugarClient = sqlSugarClient;
    }
    public void BeginTransaction(ActionExecutingContext context)
    {
        _sqlSugarClient.AsTenant().BeginTran();
    }

    public void CommitTransaction(ActionExecutedContext resultContext)
    {
        _sqlSugarClient.AsTenant().CommitTran();
    }

    public void OnCompleted(ActionExecutingContext context, ActionExecutedContext resultContext)
    {
        _sqlSugarClient.Dispose();
    }

    public void RollbackTransaction(ActionExecutedContext resultContext)
    {
        _sqlSugarClient.AsTenant().RollbackTran();
    }
}

注入到容器中:

services.AddTransient<IUnitOfWork, SqlSugarUnitOfWork>(); // 注册工作单元到容器

此时,工作单元已经添加到容器中,为了更方便的使用,我们可以使用AspNetCore框架自带的过滤器实现AOP的方式来使用工作单元。

首先需要创建工作单元特性(特性是为了方便配置哪些接口需要启用工作单元):

/// <summary>
/// 工作单元配置特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class UnitOfWorkAttribute : Attribute {}

然后创建过滤器:

/// <summary>
/// 工作单元Action过滤器
/// </summary>
public class UnitOfWorkFilter : IAsyncActionFilter, IOrderedFilter
{

    private readonly ILogger<UnitOfWorkFilter> _logger;
    public UnitOfWorkFilter(ILogger<UnitOfWorkFilter> logger)
    {
        this._logger = logger;
    }
    /// <summary>
    /// 过滤器排序
    /// </summary>
    internal const int FilterOrder = 999;

    /// <summary>
    /// 排序属性
    /// </summary>
    public int Order => FilterOrder;

    /// <summary>
    /// 拦截请求
    /// </summary>
    /// <param name="context">动作方法上下文</param>
    /// <param name="next">中间件委托</param>
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // 获取动作方法描述器
        var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
        var method = actionDescriptor.MethodInfo;

        // 获取请求上下文
        var httpContext = context.HttpContext;

        // 如果没有定义工作单元过滤器,则跳过
        if (!method.IsDefined(typeof(UnitOfWorkAttribute), true))
        {
            // 调用方法
            _ = await next();

            return;
        }

        // 打印工作单元开始消息
        _logger.LogInformation($@"{nameof(UnitOfWorkFilter)} Beginning");

        // 解析工作单元服务
        var _unitOfWork = httpContext.RequestServices.GetRequiredService<IUnitOfWork>();

        // 调用开启事务方法
        _unitOfWork.BeginTransaction(context);

        // 获取执行 Action 结果
        var resultContext = await next();

        if (resultContext == null || resultContext.Exception == null)
        {
            // 调用提交事务方法
            _unitOfWork.CommitTransaction(resultContext);
        }
        else
        {
            // 调用回滚事务方法
            _unitOfWork.RollbackTransaction(resultContext);
        }

        // 调用执行完毕方法
        _unitOfWork.OnCompleted(context, resultContext);

        // 打印工作单元结束消息  
        _logger.LogInformation($@"{nameof(UnitOfWorkFilter)} Ending");

    }
}

需要将过滤器添加到通信管道中才会起作用:

services.AddControllers(options =>
{
    // 添加 工作单元过滤器 
    options.Filters.Add<UnitOfWorkFilter>();
});

到这一步,工作单元已经准备好了。

接下来是调用方式,很简单,只要在你想启用工作单元的接口上(也就是Controller中的Action),加上工作单元的特性就可以了,像这样:

[Route("test")]
public class TestController : ControllerBase
{
    private readonly IBaseRepository<Student> _studentRepository;
    private readonly IBaseRepository<Teacher> _teacherRepository;

    public TestController(IBaseRepository<Student> studentRepository,
        IBaseRepository<Teacher> teacherRepository)
    {
        this._studentRepository = studentRepository;
        this._teacherRepository = teacherRepository;
    }

    [HttpGet, UnitOfWork]
    public async Task<IActionResult> AddTestAsync()
    {
        await _studentRepository.InsertAsync(new Student
        {
            Name = "Hello",
            Age = 22
        });

        await _teacherRepository.InsertAsync(new Teacher
        {
            Name = "World",
            Age = 35,
            Subject = 1
        });

        return Ok("Ok");
    }
}

由上面的例子可以看到,_studentRepository 和 _teacherRepository 两个仓储分别进行了添加操作,但是他们实际是在一个事务中进行的,所以如果出现异常,事务中的所有操作都会回滚,从而达到操作原子性。

为了缩减篇幅,代码我省略一部分,详细的示例项目上传到GitHub:

https://github.com/young-qiang/UnitOfWork.Demo.git

有关.NetCore基于SqlSugar的工作单元—UnitOfWork的更多相关文章

  1. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  2. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  3. ruby - 无法让 RSpec 工作—— 'require' : cannot load such file - 2

    我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳

  4. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  5. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  6. ruby - JetBrains RubyMine 3.2.4 调试器不工作 - 2

    使用Ruby1.9.2运行IDE提示说需要gemruby​​-debug-base19x并提供安装它。但是,在尝试安装它时会显示消息Failedtoinstallgems.Followinggemswerenotinstalled:C:/ProgramFiles(x86)/JetBrains/RubyMine3.2.4/rb/gems/ruby-debug-base19x-0.11.30.pre2.gem:Errorinstallingruby-debug-base19x-0.11.30.pre2.gem:The'linecache19'nativegemrequiresinstall

  7. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. ruby - `rescue $!` 是如何工作的? - 2

    我知道全局变量$!包含最新的异常对象,但我对下面的语法感到困惑。谁能帮助我理解以下语法?rescue$! 最佳答案 此构造可防止异常停止您的程序并使堆栈跟踪冒泡。它还会将该异常作为值返回,这很有用。a=get_me_datarescue$!在此行之后,a将保存请求的数据或异常。然后您可以分析该异常并采取相应措施。defget_me_dataraise'Nodataforyou'enda=get_me_datarescue$!puts"Executioncarrieson"pa#>>Executioncarrieson#>>#更现实的

  10. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

随机推荐