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) 类型特别大,不宜放 ...
随机推荐
- 【JavaWeb】学习路径1-背景
JavaWeb系列也是一个非常庞大的系列,主要分为五个部分讲解: HTML JSP和Servlet CSS的讲解 JavaScrip的讲解 jQuery框架的讲解 学习完上述内容后,就能够基本了解一个 ...
- C# for循环创建多线程
这里仅讨论Task多线程编程,不讨论其他可以使用多线程的情况,比如Beginxxx,Thread等 一般情况下,如果有多个线程需要同是启动,且每个线程中使用了集合collection中的序号. 比如参 ...
- OpenJudge 1.5.24 正常血压
24:正常血压 总时间限制: 1000ms 内存限制: 65536kB 描述 监护室每小时测量一次病人的血压,若收缩压在90 - 140之间并且舒张压在60 - 90之间(包含端点值)则称之为正常,现 ...
- ak日记 831 dxm
import sys from math import inf line = sys.stdin.readline().strip() vs = list(map(int, line.split()) ...
- 手撸Router,还要啥Router框架?react-router/vue-router躺一边去
有没有发现,在大家使用React/Vue的时候,总离不开一个小尾巴,到哪都得带着他,那就是react-router/vue-router,而基于它们的第三方框架又出现很多个性化约定和扩展,比如nuxt ...
- KingbaseES R3集群备库执行sys_backup.sh物理备份案例
案例说明: KingbaseES R3的后期版本支持通过sys_backup.sh执行sys_rman的物理备份,实际上是调用了sys_rman_v6的工具做物理备份.本案例是在备库上执行集群的备份, ...
- [Qt基础内容-08] Qt中MVC的M(Model)
Qt中MVC的M(Model)简单介绍 Qt有自己的MVC框架,分别是model(模型).view(视图).delegate(委托),这篇文章,简单的介绍以下Qt中有关model(模型)的类以及一些基 ...
- C++ 二级指针与 const 关键字
可用七种不同的方式将 const 关键字用于二级指针,如下所示: //方式一:所指一级指针指向的数据为常量,以下几种为等效表示 const int ** pptc; //方式一 int const * ...
- Python数据科学手册-机器学习: k-means聚类/高斯混合模型
前面学习的无监督学习模型:降维 另一种无监督学习模型:聚类算法. 聚类算法直接冲数据的内在性质中学习最优的划分结果或者确定离散标签类型. 最简单最容易理解的聚类算法可能是 k-means聚类算法了. ...
- 以TrueType为例谈字形描述
以TrueType为例谈字形描述 作者:哲思 时间:2022.9.17 邮箱:zhe__si@163.com GitHub:zhe-si (哲思) (github.com) 一.前言 在深入理解&qu ...