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的更多相关文章

  1. asp.net 2.0中新增的web.config的默认namespace功能 (转)

    看上去这个题目比较长,但实际上,我在看资料时发现,这就是说,在asp.net 2.0中,只需要在web.config里定义你要用的那些namespace,则在aspx页面中就不需要再象1.1那样,用 ...

  2. 【转】如何在ASP.NET 2.0中定制Expression Builders

    expressions是asp.net 2.0中的新特色,它可以使你在asp.net的页面里很方便的使用自定义的属性. 在ASPX页里只要使用$符号就可以访问到,你定制的属性了. 例如我们看个例子: ...

  3. 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 ...

  4. 【翻译】asp.net core2.0中的token认证

    原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide token ...

  5. 使用asp.net 2.0中的SqlBulkCopy类批量复制数据

    介绍:在软件开发中,把数据从一个地方复制到另一个地方是一个普遍的应用. 在很多不同的场合都会执行这个操作,包括旧系统到新系统的移植,从不同的数据库备份数据和收集数据. ASP.NET 2.0有一个Sq ...

  6. 最新版FreeTextBox(版本3.1.6)在ASP.Net 2.0中使用简介

    http://www.cnblogs.com/kflwz/articles/1337310.html   1.下载最新版FreeTextBox(版本3.1.6),解压   FreeTextBox 3. ...

  7. 在ASP.NET Core2.0中使用百度在线编辑器UEditor(转)

    一.起因 UEditor是百度旗下的富文本编辑器,对于后端上传处理仅提供了Asp.Net 版的支持. 如果想在.Net Core项目中使用,那么后台上传接口需要重构. UEditorNetCore:百 ...

  8. [转]ASP.NET 2.0中GridView无限层复杂表头的实现

    本文转自:http://blog.csdn.net/net_lover/article/details/1306211 实现方法就是给单元格填充我们想要的格式代码. C# <%@ Page La ...

  9. (转)ASP.NET 2.0中的partial

    1. 什么是局部类型? C# 2.0 引入了局部类型的概念.局部类型允许我们将一个类.结构或接口分成几个部分,分别实现在几个 不同的.cs文件中. 局部类型适用于以下情况: (1) 类型特别大,不宜放 ...

随机推荐

  1. HMS Core基于地理位置请求广告,流量变现快人一步

    对于想买车的用户来说,如果走在路上刷社交软件时突然在App里收到一条广告:"前方500米商圈里的某品牌汽车正在做优惠,力度大福利多."不管买不买,八成都会去看看,原因有三:距离近. ...

  2. KingbaseESV8R6临时表和全局临时表

    临时表概述 临时表用于存放只存在于事务或会话期间的数据.临时表中的数据对会话是私有的,每个会话只能看到和修改自己会话的数据. 您可以创建全局(global)临时表或本地(locall)临时表. 下表列 ...

  3. KingbaseESV8R6如何针对表单独设置vacuum策略

    背景 书接上文 KingbaseES应对表年龄增长过快导致事务回卷 ,有些特殊业务场景不能靠全局的autovacuum方法,例如大型数据库系统,频繁做update的系统,还有上文提到的做实时数据同步的 ...

  4. MySQL半同步复制源码解析

    今天 DBA 同事问了一个问题,MySQL在半同步复制的场景下,当关闭从节点时使得从节点的数量 < rpl_semi_sync_master_wait_for_slave_count时,show ...

  5. Java 异步编程 (5 种异步实现方式详解)

    ​ 同步操作如果遇到一个耗时的方法,需要阻塞等待,那么我们有没有办法解决呢?让它异步执行,下面我会详解异步及实现@mikechen 目录 什么是异步? 一.线程异步 二.Future异步 三.Comp ...

  6. Java的线程状态

    在我们平时写code的时候,经常会使用到多线程.其中线程所处的状态就是我们需要进程思考的问题. 线程有哪些状态 NEW: 一个线程刚被创建,但是没有被使用就是处于这个状态 RUNNABLE: 一个线程 ...

  7. 复现CVE-2022-10270(向日葵远程代码执行漏洞)

    警告 请勿使用本文提到的内容违反法律.本文不提供任何担保. 漏洞描述 向日葵是一款免费的,集远程控制电脑手机.远程桌面连接.远程开机.远程管理.支持内网穿透的一体化远程控制管理工具软件.CNVD披露了 ...

  8. 洛谷P3243 [HNOI2015]菜肴制作 (拓扑排序/贪心)

    这道题的贪心思路可真是很难证明啊...... 对于<i,j>的限制(i必须在j之前),容易想到topsort,每次在入度为0的点中选取最小的.但这种正向找是错误的,题目要求的是小的节点尽量 ...

  9. POJ3260 The Fewest Coins(混合背包)

    支付对应的是多重背包问题,找零对应完全背包问题. 难点在于找上限T+maxv*maxv,可以用鸽笼原理证明,实在想不到就开一个尽量大的数组. 1 #include <map> 2 #inc ...

  10. 二手商城集成jwt认证授权

    ------------恢复内容开始------------ 使用jwt进行认证授权的主要流程 参考博客(https://www.cnblogs.com/RayWang/p/9536524.html) ...