重新整理 .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 ...
随机推荐
- Postman(接口自动化测试)
1.Postman 接口测试参数化可能大家都非常的熟悉,但是很多人很难处理参数化后如何断言的问题,特别是当参数中出现中文时,很容易导致在 Runner 页面引入外部文件时导致中文乱码的问题,今天这篇文 ...
- Python设计模式知多少
设计模式 设计模式是前辈们经过相当长的一段时间的试验和错误总结出来的最佳实践.我找到的资料列举了以下这些设计模式:工厂模式.抽象工厂模式.单例模式.建造者模式.原型模式.适配器模式.桥接模式.过滤器模 ...
- mouseenter mouseleave鼠标悬浮离开事件
- CocoaPods 构建自己的 Pod 库
构建一个自己的库供其它人使用是不是一件很酷(苦)的事情,通过CocoaPods 可以快捷的构建自己库,并向全世界分享你的成果 一.创建 Podspec 有两种方式: 使用命令 pod lib crea ...
- vue常见错误
错误集锦 错误一 错误二 原因是写太多的import,修改呈如下方式 错误三 源码如下 原因是没有在return后面添加值 应该为 return false
- $(cd "$(dirname "$0")",pwd) 解析
xx.sh 文件内容如下: #!/bin/bash BIN_FOLDER=$(cd "$(dirname "$0")";pwd) echo $BIN_FOLDE ...
- Canal和Otter讨论二(原理与实践)
上次留下的问题 问题一: 跨公网部署Otter 参考架构图 解析 a. 数据涉及网络传输,S/E/T/L几个阶段会分散在2个或者更多Node节点上,多个Node之间通过zookeeper进行协同工 ...
- 010.Ansible_palybook 循环语句
Ansible循环语句 1 简介 我们在编写playbook的时候,不可避免的要执行一些重复性操作,比如指安装软件包,批量创建用户,操作某个目录下的所有文件等.正如我们所说,ansible一门简单的自 ...
- 小白菜Windows10系统安装Linux(ubuntu)虚拟机超详细教程(全)
注:本文"( )"中的内容可忽略 1.下载VMware(威睿 计算机虚拟化软件) 官方下载地址 默认为最新版15.1.0,我们选择立即下载 (找一个比较大的盘不要是 ...
- CentOS 7网络配置
修改配置文件 CentOS 7下的网络配置文件路径为:/etc/sysconfig/network-scripts/ifcfg-interfacename 配置文件ifcfg-interface-na ...