注册MediatR

1
2
// 注册 MediatR,用于领域事件和命令处理
services.AddMediatR(assemblies);

把指定程序集(assemblies)里实现的 MediatR 处理程序注册到依赖注入容器,这样你就可以用 MediatR 的领域事件和命令模式了。
AddMediatR是自定义扩展方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static class MediatorExtensions
{
/// <summary>
/// 扩展IServiceCollection以批量注册MediatR相关服务。
/// </summary>
/// <param name="services">依赖注入服务集合。</param>
/// <param name="assemblies">包含MediatR处理程序的程序集集合。</param>
/// <returns>服务集合本身,便于链式调用。</returns>
public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable<Assembly> assemblies)
{
// 将指定程序集中的MediatR服务注册到依赖注入容器
return services.AddMediatR(assemblies.ToArray());
}

/// <summary>
/// 派发当前DbContext中所有聚合根的领域事件。
/// </summary>
/// <param name="mediator">MediatR中介者实例。</param>
/// <param name="ctx">当前的EF Core DbContext实例。</param>
/// <returns>异步任务。</returns>
public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
{
// 获取所有实现了IDomainEvents接口且包含领域事件的实体
var domainEntities = ctx.ChangeTracker
.Entries<IDomainEvents>()
.Where(x => x.Entity.GetDomainEvents().Any());

// 收集所有领域事件
var domainEvents = domainEntities
.SelectMany(x => x.Entity.GetDomainEvents())
.ToList();

// 清空实体上的领域事件,避免重复派发
domainEntities.ToList()
.ForEach(entity => entity.Entity.ClearDomainEvents());

// 逐个通过MediatR发布领域事件
foreach (var domainEvent in domainEvents)
{
await mediator.Publish(domainEvent);
}
}
}

AddMediatR批量注册MediatR服务。
DispatchDomainEventsAsync派发 EF Core 聚合根的领域事件

  1. 找出有领域事件的实体
    ctx.ChangeTracker.Entries<IDomainEvents>() 会获取当前跟踪的、实现了 IDomainEvents 接口的实体。
    过滤出 GetDomainEvents().Any() 的实体,说明它们确实有未发布的领域事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface IDomainEvents
{
/// <summary>
/// 获取当前已注册的所有领域事件。
/// </summary>
/// <returns>领域事件的只读集合。</returns>
IEnumerable<INotification> GetDomainEvents();

/// <summary>

0/8589+0lkmmnbgfvcdx5` 8\--098765
';L';LK';LK/.,ML,KMJNHBGV;LOKIUHGFDIUY' /// 添加一个新的领域事件到集合中。
/// </summary>
/// <param name="eventItem">要添加的领域事件。</param>
void AddDomainEvent(INotification eventItem);

/// <summary>
/// 如果集合中不存在该事件,则添加一个新的领域事件。
/// 用于避免重复添加相同的事件。
/// </summary>
/// <param name="eventItem">要添加的领域事件。</param>
void AddDomainEventIfAbsent(INotification eventItem);

/// <summary>
/// 清空当前所有已注册的领域事件。
/// </summary>
void ClearDomainEvents();
}
  1. 收集所有领域事件
    SelectMany 把所有实体的事件扁平化成一个 List<IDomainEvent>。

  2. 清空实体的事件集合
    避免同一个事件被重复发布。

  3. 逐个发布事件
    mediator.Publish(domainEvent) 让 MediatR 找到对应的 INotificationHandler<T> 来处理事件。

添加工作单元过滤器

1
2
3
4
5
// 配置 MVC 选项,添加工作单元过滤器
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<UnitOfWorkFilter>();
});

ASP.NET Core 的 MVC 管道全局加了一个“工作单元(Unit of Work)过滤器”,让所有控制器/Action 执行时自动启用一个工作单元逻辑。

services.Configure<MvcOptions>
这是对 MVC 框架的配置入口,可以修改 MVC 的行为,比如格式化器、模型绑定、过滤器等。

options.Filters.Add<UnitOfWorkFilter>()
给 全局过滤器集合 添加了一个 UnitOfWorkFilter,所有 Controller Action 执行前后都会经过它。
这和在每个 Controller 上 [ServiceFilter(typeof(UnitOfWorkFilter))] 一样,但这是全局生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class UnitOfWorkFilter : IAsyncActionFilter
{
/// <summary>
/// 获取应用于控制器或方法的 UnitOfWorkAttribute 特性。
/// 优先获取控制器上的特性,如果没有则获取方法上的特性。
/// </summary>
/// <param name="actionDesc">当前 Action 的描述信息。</param>
/// <returns>UnitOfWorkAttribute 实例或 null。</returns>
private static UnitOfWorkAttribute? GetUoWAttr(ActionDescriptor actionDesc)
{
var caDesc = actionDesc as ControllerActionDescriptor;
if (caDesc == null)
{
// 不是控制器 Action,直接返回 null
return null;
}
// 优先获取控制器上的 UnitOfWorkAttribute
var uowAttr = caDesc.ControllerTypeInfo
.GetCustomAttribute<UnitOfWorkAttribute>();
if (uowAttr != null)
{
return uowAttr;
}
else
{
// 如果控制器上没有,则获取方法上的 UnitOfWorkAttribute
return caDesc.MethodInfo
.GetCustomAttribute<UnitOfWorkAttribute>();
}
}

/// <summary>
/// 拦截 Action 执行过程,实现工作单元事务控制。
/// </summary>
/// <param name="context">Action 执行上下文。</param>
/// <param name="next">委托,执行下一个中间件或 Action。</param>
/// <returns>异步任务。</returns>
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 获取当前 Action 或控制器上的 UnitOfWorkAttribute
var uowAttr = GetUoWAttr(context.ActionDescriptor);
if (uowAttr == null)
{
// 未标记 UnitOfWorkAttribute,直接执行下一个中间件
await next();
return;
}
// 启用支持异步流的事务作用域
using TransactionScope txScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
List<DbContext> dbCtxs = new List<DbContext>();
// 遍历特性中声明的所有 DbContext 类型
foreach (var dbCtxType in uowAttr.DbContextTypes)
{
// 通过依赖注入获取 DbContext 实例
var sp = context.HttpContext.RequestServices;
DbContext dbCtx = (DbContext)sp.GetRequiredService(dbCtxType);
dbCtxs.Add(dbCtx);
}
// 执行 Action
var result = await next();
if (result.Exception == null)
{
// 如果没有异常,依次保存所有 DbContext 的更改
foreach (var dbCtx in dbCtxs)
{
await dbCtx.SaveChangesAsync();
}
// 提交事务
txScope.Complete();
}
// 如果有异常,事务会自动回滚
}
}
  1. GetUoWAttr查找UnitOfWorkAttribute

UnitOfWorkAttribute自定义的特性类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// <summary>
/// 用于标记需要工作单元(Unit of Work)支持的类或方法的特性(Attribute)。
/// 通过指定一个或多个 DbContext 类型,实现对数据库操作的自动事务管理。
/// </summary>
public class UnitOfWorkAttribute : Attribute
{
/// <summary>
/// 获取需要参与工作单元的 DbContext 类型数组。
/// </summary>
public Type[] DbContextTypes { get; init; }

/// <summary>
/// 构造函数,初始化 UnitOfWorkAttribute 实例,并校验传入的类型是否为 DbContext 的子类。
/// </summary>
/// <param name="dbContextTypes">需要参与工作单元的 DbContext 类型列表。</param>
/// <exception cref="ArgumentException">如果传入的类型不是 DbContext 的子类,则抛出异常。</exception>
public UnitOfWorkAttribute(params Type[] dbContextTypes)
{
this.DbContextTypes = dbContextTypes;
foreach (var type in dbContextTypes)
{
// 校验每个类型是否为 DbContext 或其子类
if (!typeof(DbContext).IsAssignableFrom(type))
{
throw new ArgumentException($"Type {type.FullName} is not a DbContext type.");
}
}
}
}

OnActionExecutionAsync拦截 Action 方法执行,自动开启事务,确保多个 DbContext 操作要么全部成功,要么全部回滚。