重新整理 .net core 实践篇—————工作单元模式[二十六]
前言
简单整理一下工作单元模式。
正文
工作单元模式有3个特性,也算是其功能:
使用同一上下文
跟踪实体的状态
保障事务一致性
工作单元模式 主要关注事务,所以重点在事务上。
在共享层的基础建设类库中加入:
/// <summary>
/// 工作单元接口
/// </summary>
public interface IUnitOfWork : IDisposable
{
/// <summary>
/// 保存变更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>返回受影响的数据条数</returns>
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>返回保存是否成功</returns>
Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
}
SaveChangesAsync 事务第一个影响多少条数
SaveEntitiesAsync 事务是否成功
同样加入事务接口:
interface ITransaction
{
IDbContextTransaction GetCurrentTransaction();
bool HasActiveTransaction { get; }
Task<IDbContextTransaction> BeginTransactionAsync();
Task CommitTransactionAsync(IDbContextTransaction transaction);
void RollbackTransaction();
}
然后EFContext 实现它们:
/// <summary>
/// EF上下文
/// 注:在处理事务的逻辑部分,需要嵌入CAP的代码,构造函数参数 ICapPublisher
/// </summary>
public class EFContext : DbContext, IUnitOfWork, ITransaction
{
protected IMediator _mediator;
ICapPublisher _capBus;
public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus)
: base(options)
{
_mediator = mediator;
_capBus = capBus;
}
#region IUnitOfWork
/// <summary>
/// 保存实体变更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken);
// 执行发送领域事件
await _mediator.DispatchDomainEventsAsync(this);
return true;
}
///// <summary>
///// IUniOfWork中该方法的定义与DbContext中的SaveChangesAsync一致,所以此处无需再进行实现
///// </summary>
///// <param name="cancellationToken"></param>
///// <returns></returns>
//public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
//{
// return base.SaveChangesAsync();
//}
#endregion
#region ITransaction
/// <summary>
/// 当前事务
/// </summary>
private IDbContextTransaction _currentTransaction;
/// <summary>
/// 公开方法,返回当前私有事务对象
/// </summary>
/// <returns></returns>
public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;
/// <summary>
/// 当前事务是否开启
/// </summary>
public bool HasActiveTransaction => _currentTransaction == null;
/// <summary>
/// 开启事务
/// </summary>
/// <returns></returns>
public Task<IDbContextTransaction> BeginTransactionAsync()
{
if (_currentTransaction != null)
{
return null;
}
// 该扩展方法是由CAP组件提供
// 创建事务时,也要把 ICapPublisher 传入
// 核心作用是将我们要发送事件逻辑与我们业务的存储都放在同一个事务内部,从而保证事件与业务逻辑的存取都是一致的
_currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
return Task.FromResult(_currentTransaction);
}
/// <summary>
/// 提交事务
/// </summary>
/// <param name="transaction"></param>
/// <returns></returns>
public async Task CommitTransactionAsync(IDbContextTransaction transaction)
{
if (transaction == null)
{
throw new ArgumentNullException(nameof(transaction));
}
if (transaction != _currentTransaction)
{
throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");
}
try
{
// 提交事务之前,安全起见还是要 SaveChanges 一下,保存变更到数据库
await SaveChangesAsync();
transaction.Commit();
}
catch (Exception ex)
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction!=null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
/// <summary>
/// 回滚事务
/// </summary>
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction!=null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
#endregion
}
前面这两个实现了工作单元模式的事务的功能,那么还有一个问题,如何实现管理我们的事务。
/// <summary>
/// 注入事务管理过程
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
/// <typeparam name="TRequest"></typeparam>
/// <typeparam name="TResponse"></typeparam>
public class TransactionBehavior<TDbContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TDbContext : EFContext
{
ILogger _logger;
TDbContext _dbContext;
ICapPublisher _capBus;
public TransactionBehavior(TDbContext dbContext, ICapPublisher capBus, ILogger logger)
{
_dbContext = dbContext ?? throw new ArgumentNullException();
_capBus = capBus ?? throw new ArgumentNullException(nameof(capBus));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 事务执行
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var response = default(TResponse);
var typeName = request.GetGenericTypeName();
try
{
// 判断当前是否有开启事务,如果开启就执行后续动作
if (_dbContext.HasActiveTransaction)
{
return await next();
}
// 数据库操作默认执行策略
// 比如,可以嵌入重试逻辑
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
// 开启事务
Guid transactionId;
using (var transaction = await _dbContext.BeginTransactionAsync())
// 记录开启的事务
using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
{
_logger.LogInformation("----- 开始事务 {TransactionId} ({@Command})", transaction.TransactionId, typeName, request);
// 类似中间件模式,后续逻辑执行完成后,提交事务
response = await next();
_logger.LogInformation("----- 提交事务 {TransactionId} ({CommandName})", transaction.TransactionId, typeName);
// 提交事务
await _dbContext.CommitTransactionAsync(transaction);
transactionId = transaction.TransactionId;
}
});
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理事务出错 {CommandName} ({@Command})", typeName, request);
throw;
}
}
}
这里可能会有点疑问,这里没有rollback啊。
using (var transaction = await _dbContext.BeginTransactionAsync())
这一句是托管了,如果中间发生异常,那么会自动调用rollback,using原理前面在c# 基础篇中介绍了,本质就是try catch finnaly这样的模式,这里不详细介绍了。
结
下一节仓储层的具体实现。
重新整理 .net core 实践篇—————工作单元模式[二十六]的更多相关文章
- 重新整理 .net core 实践篇————依赖注入应用[二]
前言 这里介绍一下.net core的依赖注入框架,其中其代码原理在我的另一个整理<<重新整理 1400篇>>中已经写了,故而专门整理应用这一块. 以下只是个人整理,如有问题, ...
- 重新整理 .net core 实践篇—————服务与配置之间[十一二]
前言 前面基本介绍了,官方对于asp .net core 设计配置和设计服务的框架的一些思路.看下服务和配置之间是如何联系的吧. 正文 服务: public interface ISelfServic ...
- 重新整理 .net core 实践篇————polly失败重试[三十四]
前言 简单整理一下polly 重试. 正文 在开发程序中一般都有一个重试帮助类,那么polly同样有这个功能. polly 组件包: polly 功能包 polly.Extensions.Http 专 ...
- 重新整理 .net core 实践篇————配置系统——军令(命令行)[六]
前言 前文已经基本写了一下配置文件系统的一些基本原理.本文介绍一下命令行导入配置系统. 正文 要使用的话,引入Microsoft.extensions.Configuration.commandLin ...
- 重新整理 .net core 实践篇—————日志系统之战地记者[十五]
前言 本节开始整理日志相关的东西.先整理一下日志的基本原理. 正文 首先介绍一下包: Microsoft.Extengsion.Logging.Abstrations 这个是接口包. Microsof ...
- 重新整理 .net core 实践篇—————3种配置验证[十四]
前言 简单整理一些配置的验证. 正文 配置的验证大概分为3类: 直接注册验证函数 实现IValidteOptions 使用Microsoft.Extensions.Options.DataAnnota ...
- 重新整理 .net core 实践篇—————路由和终结点[二十三]
前言 简单整理一下路由和终节点. 正文 路由方式主要有两种: 1.路由模板方式 2.RouteAttribute 方式 路由约束: 1.类型约束 2.范围约束 3.正则表达式 4.是否必选 5.自定义 ...
- 重新整理 .net core 实践篇————配置应用[一]
前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...
- 重新整理 .net core 实践篇—————仓储层的具体实现[二十七]
前言 简单整理一下仓储层. 正文 在共享层的基础建设类库中: /// <summary> /// 泛型仓储接口 /// </summary> /// <typeparam ...
随机推荐
- 006-Java的访问权限控制符和包导入机制
目录 一.Java的访问权限控制符 一.访问控制符的作用 二.访问控制符的分类 二.Java的包导入机制 一.为什么要使用package? 二.package怎么用? 三.对于带有package的ja ...
- 2020中国大学生程序设计竞赛(CCPC) - 网络选拔赛总结
1003 Express Mail Taking 题意:有n个柜子(编号1-n),m封信,k号位置有钥匙,现在需要取信封,并且每取一次信封都要从k号位置进行领取一次钥匙,再去有信封的位置领取信封,问最 ...
- 将mysql数据同步到ES6.4(全量+增量)
下载安装包时注意下载到指定文件夹 这里我放在OPT文件夹下一:安装logstash进入到opt文件夹打开终端 执行以下命令wget -c https://artifacts.elastic.co/do ...
- CRM系统实现自动化的“三部曲”
在了解CRM系统的自动化的时候,我们先来看一下CRM能干什么. 从上面的流程图我们就可以看出,CRM可以管理售前,售中和售后的整个客户生命周期. 为什么在复杂的客户生命周期中需要自动化呢? 当然是为了 ...
- Java中NIO的简单介绍
NIO基本介绍 Java NIO(New IO) 也有人称之为Java non-blocking IO 是从Java1.4版本开始引入的一个新的IO API,可以代替标准的IO API.NIO与原来的 ...
- 【建议收藏】缺少 Vue3 和 Spring Boot 的实战项目经验?我这儿有啊!
缺少 Vue3 和 Spring Boot 的实战项目经验?缺少学习项目和练手项目?我这儿有啊! 从 2019 年到 2021 年,空闲时间里陆陆续续做了一些开源项目,推荐给大家啊!记得点赞和收藏噢! ...
- 使用基于centos7 dbus问题总结
Authorization not available. Check if polkit Authorization not available. Check if polkit service is ...
- bash调试 脚本第一行 加set -x #!/bin/expact -d
#!/bin/bash set -x 脚本第一行加 #!/bin/expact -d
- date命令月日时分年
# date +%Y/%m/%d2019/09/29[root@a-3e5 lpts-20190910-keyan-v0.2]# date +%H:%M20:00
- 简单读读源码 - dubbo多提供者(provider)配置方法
简单读读源码 - dubbo多提供者(provider)配置方法 消费者端dubbo的yml配置 dubbo: consumer: timeout: 300000 protocol: name: du ...