关键词:
前言
简单整理一下工作单元模式。
正文
工作单元模式有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这样的模式,这里不详细介绍了。
结
下一节仓储层的具体实现。
重新整理.netcore实践篇—————应用层[三十](代码片段)
前言简单介绍一下应用层。正文应用层用来做什么的呢?应用层用来做处理api请求的。[HttpPost]publicTask<long>CreateOrder([FromBody]CreateOrderVeiwModelviewModel) varmodel=viewModel.ToModel(); returnawaitorderService.CreateOrder(model); 查看详情
重新整理.netcore实践篇—————静态中间件[二十一](代码片段)
前言简单整理一下静态中间件。正文我们使用静态文件调用:app.UseStaticFiles();那么这个默认会将我们根目录下的wwwroot作为静态目录。这个就比较值得注意的,可能刚开始学.netcore的小伙伴,会直接把脚本写在更目录script这样是访... 查看详情
重新整理.netcore实践篇—————仓储层的具体实现[二十七](代码片段)
前言简单整理一下仓储层。正文在共享层的基础建设类库中:///<summary>///泛型仓储接口///</summary>///<typeparamname="TEntity">实体类型</typeparam>publicinterfaceIRepository<TEntity>whereTEntity:Entity,IAggregate 查看详情
重新整理.netcore实践篇—————路由和终结点[二十三](代码片段)
前言简单整理一下路由和终节点。正文路由方式主要有两种:1.路由模板方式2.RouteAttribute方式路由约束:1.类型约束2.范围约束3.正则表达式4.是否必选5.自定义IRootConstaintURL生成1.LinKGenerator2.IUrlHelper先搭建一个swagger:services.AddSwaggerGen(... 查看详情
重新整理.netcore实践篇—————应用分层[二十四](代码片段)
前言简单整理一下分层。正文应用程序分层,分为:1.领域模型层2.基础设施层3.应用层4.共享层共享层共享层一般包括下面几个类库。有一个Core的类库,比如说BLog.Core.这个类库用来,主要用来承载一些基础简单的类型,比如说一... 查看详情
重新整理.netcore实践篇—————entity的定义[二十五](代码片段)
前言简单介绍一下实体模型的设计。正文前文提及了我们的应用分为:共享层基础设施层领域层应用层今天来介绍领域模型层。前文提及到领域模型在共享层有一个领域模型抽象类库。里面有这些类:先分别介绍一下这些类是做... 查看详情
重新整理.netcore实践篇—————微服务的桥梁eventbus[三十一](代码片段)
前言简单介绍一下EventBus.正文EventBus也就是集成事件,用于服务与服务之间的通信。比如说我们的订单处理事件,当订单处理完毕后,我们如果通过api马上去调用后续接口。比如说订单完成给用户通知的话,如果是大量订单,即... 查看详情
重新整理.netcore实践篇—————日志系统之结构化[十八](代码片段)
前言什么是结构化呢?结构化,就是将原本没有规律的东西进行有规律话。就比如我们学习数据结构,需要学习排序然后又要学习查询,说白了这就是一套,没有排序,谈如何查询是没有意义的,因为查询算法就是根据某种规律... 查看详情
重新整理.netcore实践篇—linux上排查问题实用工具[外篇]
前言介绍下面几个工具:Lldbcreatedumpdotnet-dumpdotnet-gcdumpdotnet-symbolProcdump该文的前置篇为:https://www.cnblogs.com/aoximin/p/16839812.html献给初学者,这篇就只介绍下看下日志和lldb,毕竟东西太多了。正文我以官网的例子作为演示:... 查看详情
asp.netcore中间件应用实践中你不知道的那些事(代码片段)
...工作原理的同学,可以点击查看以下两篇解读文章:Asp.NetCoreEndPoint终结点路由工作原理解读ASP.NETCORE管道模型及中间件使用解读1.1中间件(Middleware)的作用我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请... 查看详情
.netcore杂记
...解了.netcore设计理念和设计思想(纯属跟人理解)。再此整理了之前写的一些学习笔记,后续也会把新的学习新的加上。1..netcore跨平台实践2.asp.netcore使用EF7CodeFirst创建数据库,同时使用命令创建数据库 查看详情
.netcore微服务容器系列基础目录篇
...今天只是先把目录列出来,后面的每篇文章这两天会进行重新修改的,目前先将就看下)。简单介绍一下,博主目前就职于某电商公司,目前工作用的是.netcore,业余时间也会看下Java,公司内部目前也是多语言并存,毕竟很多工... 查看详情
.netcore实践篇————网关
参考技术A简单整理一下网关。在介绍网关之前,介绍一下BFF,BFF全称是BackendForFrontend,它负责认证授权,服务聚合,目标是为前端提供服务。说的通透一点,就是有没有见过这种服务。上述就是buff通过代理其他服务来让前端访... 查看详情
asp.netcore分布式项目实战整理identityserver4mvc授权consent功能实现(代码片段)
...前言由于之前的博客都是基于其他的博客进行开发,现在重新整理一下方便以后后期使用与学习新建IdentityServer4服务端服务端也就是提供服务,如Q 查看详情
ASP.Net Core:更改密码时数据库连接失败,但重新启动应用程序时可以工作
】ASP.NetCore:更改密码时数据库连接失败,但重新启动应用程序时可以工作【英文标题】:ASP.NetCore:Databaseconnectionfailswhenthepasswordischanged,butworkswhentheapplicationisrestarted【发布时间】:2021-11-2304:56:53【问题描述】:我们有一个ASP.NetCo... 查看详情
.netcore跨平台实践
本人采用Ubuntu14.04来实现.netcore的跨平台实践。首先安装Ubuntu14.04系统。安装细节问百度。1..netcoreconsole程序的跨平台首先新建一个console程序在一个空目录下用dotnet命令行创建 修改project.json文件在命令上中dotnetrestore(还原包)在... 查看详情
.netcore多租户框架整理
一saaskitAsp.NetCoremulti-tenantapplicationSampleusing#SaaSKithttps://github.com/saaskit/saaskithttp://benfoster.io/blog/tagged/saaskitsaaskitsampleshttps://github.com/janaks09/NetCoreSaaS/tree/0e1bdab 查看详情
netcore国际化最佳实践(代码片段)
NetCore国际化最佳实践ASP.NETCore中提供了一些本地化服务和中间件,可将网站本地化为不同的语言文化。ASP.NETCore中我们可以使用Microsoft.AspNetCore.Localization库来实现本地化。但是默认只支持使用资源文件方式做多语言存储,... 查看详情