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>继承该类并返回UIRequestHandler<T>实现该接口并返回Task<Unit>AsyncRequestHandler<T>继承该类并返回TaskRequestHandler<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) 类型特别大,不宜放 ...
随机推荐
- wallpaperPKG文件提取
简单粗暴 下载这个ZIP文件链接: 下载地址戳我 提取码: ag43 解压后双击打开如下文件 我们在解压一下repkg-master.zip解压后如下,注意我的路径进入到这些很多文件的页面 返回首页复 ...
- django 通过MQTT连接阿里云
Django MQTT 连接阿里云 目录 Django MQTT 连接阿里云 目录 一.安装库 1.安装Python对接mqtt协议库,paho-mqtt 二. 设备认证,一机一密型接入 三.问题 1 ...
- KingbaseES V8R6 维护管理案例之---Kstudio在CentOS 7启动故障
案例说明: 在CentOS 7上安装KingbaseES V8R6C006数据库后,启动Kstudio图形界面启动失败,gtk动态库加载失败,安装gtk相关动态库后,问题解决. 适用版本: Kin ...
- 【读书笔记】C#高级编程 第十二章 动态语言扩展
(一)DLR C#4的动态功能是Dynamic Language Runtime(动态语言运行时,DLR)的一部分.DLR是添加到CLR的一系列服务. (二)dynamic类型 dynamic类型允许 ...
- DOS文档
- Python 第四次实验
1.(程序设计)定义函数def GetRandomChar(),返回一个随机的数字或大写或小写字母,每个符号出现的机会相等.调用该函数8次,生成并输出一个8位的验证码.用户输入该验证码,如果验证码正确 ...
- 关于指针初始化为NULL的一些问题
关于指针初始化问题,先看以下代码: #include <stdio.h>typedef struct{ char data[128]; int top;} Stack;voi ...
- .env[mode]文件中如何添加注释
前言 Vue-Cli 允许我们在项目根目录创建.env.[mode]文件来设置一些打包编译的启动参数,通过执行脚本的时候加mode参数,指定不同环境需要加载的配置文件 形如: .env.prod NO ...
- 【项目实战】Kaggle电影评论情感分析
前言 这几天持续摆烂了几天,原因是我自己对于Kaggle电影评论情感分析的这个赛题敲出来的代码无论如何没办法运行,其中数据变换的维度我无法把握好,所以总是在函数中传错数据.今天痛定思痛,重新写了一遍代 ...
- Beats: 使用 Filebeat 进行日志结构化
文章转载自:https://blog.csdn.net/UbuntuTouch/article/details/106688240 生产一个叫做 json_logs 的文件: {"user_ ...