Asp.Net Core6.0中MediatR的应用CQRS
1、前言
对于简单的系统而言模型与数据可以进行直接的映射,比如说三层模型就足够支撑项目的需求了。对于这种简单的系统我们过度设计说白了无异于增加成本,因为对于一般的CRUD来说我们不用特别区分查询和增删改的程序结构。高射炮打蚊子那就有点大材小用了。但是我们的系统具有一定复杂性的时候,可能源于访问频次、数据量或者数据模型这个时候我们的查询跟增删改的需求差距就逐渐变大。所以CQRS(Command Query Responsibility Segregation)命令查询的责任分离就出现了。CQRS本质上是一种读写分离设计思想,这种框架设计模式将命令型业务和查询型业务分开单独处理。我们运用MediatR就可以轻松的实现CQRS。
2、中介者模式
中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系,中介者使各个对象之间不需要显式地相互引用,从而降低耦合性。也符合符合迪米特原则。MediatR本质就是中介者模式,实现命令的构造和命令的处理分开来。
3、MediatR简介
MediatR是一个跨平台通过一种进程内消息传递机制,进行请求/响应、命令、查询、通知和事件的消息传递,并通过C#泛型来支持消息的智能调度,其目的是消息发送和消息处理的解耦。它支持以单播和多播形式使用同步或异步的模式来发布消息,创建和侦听事件。
4、主要的几个对象
a.IMediator:主要提供Send与Publish方法,需要执行的命令都是通过这两个方法实现
b.IRequest、IRequest<T>:命令查询 | 处理类所继承的接口,一个有返回类型,一个无返回类型,一个查询对应一个处理类,程序集只认第一个扫描到的类。
c.IRequestHandler<in TRequest,TResponse>(实现Handle方法) :命令处理接口。命令查询 | 处理类继承它,也可以继承AsyncRequestHandler(实现抽象Handle方法)、RequestHandler(实现抽象Handle方法)接口
d.INotification:命令查询 | 处理类所继承的接口这个没有返回,与IRequest不通的是可以对于多个处理类。
e.INotificationHandler<in TNotification>:与IRequestHandler一样的只不过这是INotification的处理接口
5、IRequest栗子
a.IRequest<T>:有返回值的类
说了那么多干巴巴的直接上代码看。我这里是Core6.0控制台应用程序,安装nuget包 MediatR与扩展包MediatR.Extensions.Microsoft.DependencyInjection。也可以通过命令行添加dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
先看命令的查询处理
这里我习惯性的将两个类放在一个文件里面方便查看,命名这里做查询就写XXXQuery 处理类的命名也是XXXQueryHandler
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Mrdiator.Query
{
/// <summary>
/// 查询信息命令类
/// </summary>
internal class GetInfoQuery:IRequest<Result>
{
/// <summary>
/// 构造函数--就是查询的条件说白了
/// </summary>
/// <param name="age"></param>
/// <param name="name"></param>
/// <param name="nowTime"></param>
internal GetInfoQuery(int age, string name, DateTime nowTime)
{
Age = age;
Name = name;
NowTime = nowTime;
} public int Age { get; set; }
public string Name { get; set; }
public DateTime NowTime { get; set; }
}
/// <summary>
/// 查询命令的处理类
/// </summary>
internal class GetInfoQueryHandller : IRequestHandler<GetInfoQuery, Result>
{
public Task<Result> Handle(GetInfoQuery request, CancellationToken cancellationToken)
{
Console.WriteLine("GetObjCommandHandller");
object ret = new
{
request.Name,
request.NowTime,
request.Age,
};
var result = new Result()
{
Code = 200,
Message="Success",
Data = ret
};
return Task.FromResult(result);
}
}
}
来看一下调用
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Mrdiator.Query;
using Newtonsoft.Json;
using System.Net.Http;
using System.Reflection; //实例化一个ServiceCollection
IServiceCollection services = new ServiceCollection();
//添加当前的程序集MediatR会扫描当前的程序集
//services.AddMediatR(typeof(Program).Assembly);
services.AddMediatR(Assembly.GetExecutingAssembly());
//构建一个serviceProvider
var serviceProvider = services.BuildServiceProvider();
//从容器中获取mediator
var mediator = serviceProvider.GetService<IMediator>();
//执行命令
var result =await mediator.Send(new GetInfoQuery(18,"wyy",DateTime.Now));
同样我们启动程序也打印了我们的输出。
b.IRequest:无返回值的栗子
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Mrdiator.Query
{
/// <summary>
/// 命令查询类--无返回值
/// </summary>
internal class GetInfoQuery2 : IRequest
{
public GetInfoQuery2(int age, string name, DateTime nowTime)
{
Age = age;
Name = name;
NowTime = nowTime;
} public int Age { get; set; }
public string Name { get; set; }
public DateTime NowTime { get; set; }
}
/// <summary>
/// 命令处理类1-----继承AsyncRequestHandler 实现抽象方法 Handle
/// </summary>
internal class GetInfoQuery2_2Handller : AsyncRequestHandler<GetInfoQuery2>
{
protected override Task Handle(GetInfoQuery2 request, CancellationToken cancellationToken)
{
Console.WriteLine("GetInfoQuery2_2Handller");
return Task.CompletedTask;
}
}
/// <summary>
/// 命令处理类2-----IRequestHandler 实现接口方法 Handle
/// </summary>
internal class GetInfoQuery2Handller : IRequestHandler<GetInfoQuery2>
{
public Task<Unit> Handle(GetInfoQuery2 request, CancellationToken cancellationToken)
{
Console.WriteLine("GetInfoQuery2Handller"); return Task.FromResult(new Unit());
}
} }
var result2 =await mediator.Send(new GetInfoQuery2(18,"wyy",DateTime.Now));
我们写了一个GetInfoQuery2,下面有两个类都在泛型里实现了,可以看到程序是只执行了GetInfoQuery2_2Handller就可以看出IRequest命令类跟处理类失忆对一的关系。我们只是通过Mediator的send将GetInfoQuery2 作为参数传进去程序就能执行到GetInfoQuery2_2Handller里面的Handle方法这就是MediatR的好处。
/// <summary>
/// 命令处理类-----继承RequestHandler 实现抽象方法 Handle
/// </summary>
internal class GetInfoQuery3Handller : RequestHandler<GetInfoQuery3, Result>
{
protected override Result Handle(GetInfoQuery3 request)
{
Console.WriteLine("GetInfoQuery3Handller");
return new Result();
}
}
这样写也可以调用到 ,这就是上面写的 继承不同的类或者接口,一般大多数我都是继承IRequestHandler。
6、INotification栗子
这里我新建了一个Core6.0的WebAPI的工程来演示INotification的运用。同样的nuget安装MediatR与扩展包MediatR.Extensions.Microsoft.DependencyInjection。在Program.cs里添加。这里如果你的命令处理类跟项目在同一个程序集里面就用第二个也可以,如果你是分开的另外建了一个类库写命令查询的直接引用里面随便一个类获取程序集就可以了
//获取该类下的程序集
builder.Services.AddMediatR(typeof(Program).Assembly);
//获取当前程序集
//builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
这里我们注册了处理多个事件、每个都执行到了。
using MediatApi.Helper;
using MediatApi.Model;
using MediatR;
using Newtonsoft.Json; namespace MediatApi.Application.Command
{
/// <summary>
/// 创建订单
/// </summary>
public class OrderCreateCommand:INotification
{
/// <summary>
/// Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string? OrderNum { get; set; }
/// <summary>
/// 订单类型
/// </summary>
public string? OrderType { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreatTime { get; set; } }
/// <summary>
/// 创建订单处理1
/// </summary>
public class OrderCreateCommandHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Order model = new(notification.Id, notification.OrderNum, notification.OrderType, notification.CreatTime);
//数据库操作省略
Result ret = new()
{
Code=200,
Message="",
Data=model
};
string retJson=JsonConvert.SerializeObject(ret);
Console.WriteLine("11111——————————————订单创建啦!");
return Task.FromResult(retJson);
}
}
/// <summary>
/// 创建订单后处理步骤2
/// </summary>
public class OrderCreateTwoHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("22222——————————————扣钱成功了");
return Task.CompletedTask;
}
}
/// <summary>
/// 创建订单后处理步骤3
/// </summary>
public class OrderCreateThreeHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("333333333——————————————订单入库啦!");
return Task.CompletedTask;
}
}
/// <summary>
/// 创建订单后处理步骤4
/// </summary>
public class OrderCreateFoureHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("4444444——————————————第四个操作呢!");
return Task.CompletedTask;
}
}
/// <summary>
/// 创建订单后处理步骤5
/// </summary>
public class OrderCreateFiveHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("55555——————————————接着奏乐接着舞!");
return Task.CompletedTask;
}
} }
注意:这里是用mediator的publish方法的实现的,命令查询类继承INotification就要用publish方法,继承IRequest就要用Send方法,项目目录也在左侧这样别人看着也清晰点
7、IPipelineBehavior
这个接口的作用就是在我们命令处理之前或者之后插入逻辑,类似我们的中间件,我新建一个TransactionBehavior来处理保存数据库之前之后的操作,这里的代码只判断了是否为空之前写的是判断事务是否为空,代码多就随便写了意思意思。
然后新建一个DBTransactionBehavior这里对操作数据库新增演示一下
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace MediatApi.Helper
{
public class TransactionBehavior<TDBContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TDBContext : WyyDbContext
{
ILogger _logger;
WyyDbContext _dbContext; public TransactionBehavior(ILogger logger, WyyDbContext dbContext)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
} public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var response = default(TResponse);
try
{
Console.WriteLine("执行前逻辑++++++++++++++++++++++++++++++++++");
Console.WriteLine();
if (request != null)
return await next(); Console.WriteLine("逻辑不对处理++++++++++++++++++++++++++++++++");
Console.WriteLine();
var strategy = _dbContext.Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async () =>
{
Guid transactionId;
using (var transaction = await _dbContext.Database.BeginTransactionAsync())
using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
{
_logger.LogInformation("----- 开始事务 {TransactionId} 请求{request}", transaction.TransactionId, request); response = await next(); _logger.LogInformation("----- 提交事务 {TransactionId}}", transaction.TransactionId); await _dbContext.CommitTransactionAsync(transaction); transactionId = transaction.TransactionId;
}
}); return response; }
catch(Exception ex)
{
_logger.LogError(ex, "处理事务出错{@Command}", request); throw;
}
}
}
}
using MediatApi.Entity;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage; namespace MediatApi.Helper
{
public class WyyDbContext: testContext
{
private IDbContextTransaction _currentTransaction; public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;
public bool HasActiveTransaction => _currentTransaction != null; 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
{
await SaveChangesAsync();
transaction.Commit();
}
catch
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
}
}
在Program里面注册服务
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DBTransactionBehavior<,>));
这里链接数据库我们做一个新增Command里面的testContext就是数据库上下文我这里是从数据库直接生成的 WyyDbContext继承testContext
using MediatApi.Entity;
using MediatR; namespace MediatApi.Application.Command
{
public class CusCreateCommand:IRequest<int>
{
public string? Name { get; set; }
public int? Age { get; set; }
}
public class CusCreateCommandHandler : IRequestHandler<CusCreateCommand, int>
{
private readonly testContext _db; public CusCreateCommandHandler(testContext db)
{
_db = db;
} public async Task<int> Handle(CusCreateCommand request, CancellationToken cancellationToken)
{
Cu c = new()
{
Name = request.Name,
Age = request.Age, };
_db.Cus.Add(c);
Console.WriteLine("执行处理++++++++++++++++++++++++++++++++++");
return await _db.SaveChangesAsync();
}
}
}
为了增加对比性 我也新建了一个传统的services来新增Cus
using MediatApi.Entity; namespace MediatApi.services
{ public interface ICusService
{
Task<int> AddAsync();
}
public class CusService : ICusService
{
private readonly testContext _db; public CusService(testContext db)
{
_db = db;
} public async Task<int> AddAsync()
{
Cu c = new()
{
Name = "wyy",
Age = 18 };
_db.Cus.Add(c);
return await _db.SaveChangesAsync();
}
}
}
控制器里面两个新增 一个走MediatRy个走传统的Service
/// <summary>
/// 创建用户_mediator
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
[HttpPost]
public async Task<int> CusCreateMediator([FromBody] CusCreateCommand cmd)=> await _mediator.Send(cmd,HttpContext.RequestAborted);
/// <summary>
/// 创建用户 Service
/// </summary>
/// <returns></returns> [HttpPost]
public async Task<int> CusCreateService() => await _cusService.AddAsync();
运行可以发现 传统的Service就不会执行。MediatR
中具有与此类似的管线机制,可通过泛型接口 IPipelineBehavior<,>注册,使得我们在 Handle 真正执行前或后可以额外做一些事情:记录日志、对消息做校验、对数据做预处理数据库事务、记录性能较差的Handler 等等。
8、总结
MediatR的用法
a.IRequest、IRequest<T> 只有一个单独的Handler执行
b.Notification
,用于多个Handler。
对于每个 request 类型,都有相应的 handler 接口:
IRequestHandler<T, U>
实现该接口并返回Task<U>
RequestHandler<T, U>
继承该类并返回U
IRequestHandler<T>
实现该接口并返回Task<Unit>
AsyncRequestHandler<T>
继承该类并返回Task
RequestHandler<T>
继承该类不返回
Notification
Notification
就是通知,调用者发出一次,然后可以有多个处理者参与处理。
Notification
消息的定义很简单,只需要让你的类继承一个空接口 INotification
即可。而处理程序则实现 INotificationHandler<T>
接口的 Handle
方法
PASS:如果你想成为一个成功的人,那么请为自己加油,让积极打败消极,让高尚打败鄙陋,让真诚打败虚伪,让宽容打败褊狭,让快乐打败忧郁,让勤奋打败懒惰,让坚强打败脆弱,只要你愿意,你完全可以做最好的自己。
Asp.Net Core6.0中MediatR的应用CQRS的更多相关文章
- asp.net 2.0中新增的web.config的默认namespace功能 (转)
看上去这个题目比较长,但实际上,我在看资料时发现,这就是说,在asp.net 2.0中,只需要在web.config里定义你要用的那些namespace,则在aspx页面中就不需要再象1.1那样,用 ...
- 【转】如何在ASP.NET 2.0中定制Expression Builders
expressions是asp.net 2.0中的新特色,它可以使你在asp.net的页面里很方便的使用自定义的属性. 在ASPX页里只要使用$符号就可以访问到,你定制的属性了. 例如我们看个例子: ...
- asp.net MVC3.0 中@Html.Partial,@Html.Action,@Html.RenderPartial,@Html.RenderAction
asp.net MVC3.0 中@Html.Partial,@Html.Action,@Html.RenderPartial,@Html.RenderAction 1.带有Render的方法返回值是v ...
- 【翻译】asp.net core2.0中的token认证
原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide token ...
- 使用asp.net 2.0中的SqlBulkCopy类批量复制数据
介绍:在软件开发中,把数据从一个地方复制到另一个地方是一个普遍的应用. 在很多不同的场合都会执行这个操作,包括旧系统到新系统的移植,从不同的数据库备份数据和收集数据. ASP.NET 2.0有一个Sq ...
- 最新版FreeTextBox(版本3.1.6)在ASP.Net 2.0中使用简介
http://www.cnblogs.com/kflwz/articles/1337310.html 1.下载最新版FreeTextBox(版本3.1.6),解压 FreeTextBox 3. ...
- 在ASP.NET Core2.0中使用百度在线编辑器UEditor(转)
一.起因 UEditor是百度旗下的富文本编辑器,对于后端上传处理仅提供了Asp.Net 版的支持. 如果想在.Net Core项目中使用,那么后台上传接口需要重构. UEditorNetCore:百 ...
- [转]ASP.NET 2.0中GridView无限层复杂表头的实现
本文转自:http://blog.csdn.net/net_lover/article/details/1306211 实现方法就是给单元格填充我们想要的格式代码. C# <%@ Page La ...
- (转)ASP.NET 2.0中的partial
1. 什么是局部类型? C# 2.0 引入了局部类型的概念.局部类型允许我们将一个类.结构或接口分成几个部分,分别实现在几个 不同的.cs文件中. 局部类型适用于以下情况: (1) 类型特别大,不宜放 ...
随机推荐
- 【JDBC】学习路径10-c3p0数据源的使用(JDBC完结)
第一章:下载 c3p0官网:https://www.mchange.com/projects/c3p0/ 这个是SourceForge提供的下载地址:https://sourceforge.net/p ...
- 回溯剪枝,dfs,bfs
dfs: 给定一个整数n,将数字1~n排成一排,将会有很多种排列方法. 现在,请你按照字典序将所有的排列方法输出. 输入格式 共一行,包含一个整数n. 输出格式 按字典序输出所有排列方案,每个方案占一 ...
- RabbitMQ之消息模式简单易懂,超详细分享~~~
前言 上一篇对RabbitMQ的流程和相关的理论进行初步的概述,如果小伙伴之前对消息队列不是很了解,那么在看理论时会有些困惑,这里以消息模式为切入点,结合理论细节和代码实践的方式一起来学习. 正文 常 ...
- 我的Go gRPC之旅、01 初识gRPC,感受gRPC的强大魅力
微服务架构 微服务是一种开发软件的架构和组织方法,其中软件由通过明确定义的API 进行通信的小型独立服务组成. 这些服务由各个小型独立团队负责. 微服务架构使应用程序更易于扩展和更快地开发,从而加速创 ...
- 前端必读3.0:如何在 Angular 中使用SpreadJS实现导入和导出 Excel 文件
在之前的文章中,我们为大家分别详细介绍了在JavaScript.React中使用SpreadJS导入和导出Excel文件的方法,作为带给广大前端开发者的"三部曲",本文我们将为大家 ...
- Mysqldump 的 的 6 大使用场景的导出命令
Mysqldump 选项解析 场景描述 1. 导出 db1.db2 两个数据库的所有数据. mysqldump -uroot -p -P8635 -h192.168.0.199 --hex-blob ...
- Logstash:导入zipcode CSV文件和Geo Search体验
- 第六章:Django 综合篇 - 3:使用MySQL数据库
在实际生产环境,Django是不可能使用SQLite这种轻量级的基于文件的数据库作为生产数据库.一般较多的会选择MySQL. 下面介绍一下如何在Django中使用MySQL数据库. 一.安装MySQL ...
- Linux恢复误删除的文件或者目录
文章转载自:https://www.jianshu.com/p/662293f12a47 linux不像windows有个回收站,使用rm -rf *基本上文件是找不回来的. 那么问题来了: 对于li ...
- 解决inode满
登陆服务器运行df -i 然后运行 for i in /*; do echo $i; find $i |wc -l|sort -nr; done 看看每个文件夹下面的数量 最后发现是/var/spoo ...