You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MediatR封闭泛型EventHandler未被调用问题求助

MediatR泛型事件处理器无法触发的问题

我在项目中用MediatR的Notification实现缓存失效,针对各实体定义了Created、Updated、Deleted三类事件。当前遇到的问题:直接注册泛型UpdatedEventHandler实例时,处理器始终不会被调用;但创建继承自该泛型处理器的显式类后,相同的注册代码就能正常触发Handle方法。

代码详情

1. 事件处理器注册代码

collection.AddTransient(
    typeof(IEventHandler<UpdatedEvent<RefreshToken, RefreshTokenId>>),
    typeof(UpdatedEventHandler<IRefreshTokenRepository, RefreshToken, RefreshTokenId, IRefreshTokenDto, UpdatedEvent<RefreshToken, RefreshTokenId>>));

2. 通知发布代码

await _mediator.Publish(new UpdatedEvent<RefreshToken, RefreshTokenId>(new RefreshToken(new UserId())), cancellationToken);

3. 泛型事件处理器实现

public class UpdatedEventHandler<TIRepository, TEntity, TId, TIDto, TUpdated> : IEventHandler<TUpdated> 
    where TUpdated : UpdatedEvent<TEntity, TId>
    where TIRepository : IRepository<TEntity, TId, TIDto>
    where TEntity : Entity<TId>
    where TId : IdObject<TId>
    where TIDto : IDto<TId>
{
    private readonly TIRepository _repository;

    public UpdatedEventHandler(TIRepository repository)
    {
        _repository = repository;
    }

    public Task Handle(TUpdated updatedEvent, CancellationToken cancellationToken)
    {
        var _cacheKeys = GetBaseCacheKeysAsync(updatedEvent);

        return _repository.ClearCacheAsync(_cacheKeys);
    }

    private async IAsyncEnumerable<string> GetBaseCacheKeysAsync(TUpdated updatedEvent)
    {
        yield return await _repository.EntityValueCacheKeyAsync(nameof(_repository.GetByIdAsync),
            updatedEvent.Updated.Id.Value.ToString());
        yield return await _repository.EntityCacheKeyAsync(nameof(_repository.GetListAsync));
        
        await foreach (var cacheKey in GetCacheKeysAsync(updatedEvent))
        {
            yield return cacheKey;
        }
    }

    protected virtual IAsyncEnumerable<string> GetCacheKeysAsync(TUpdated updatedEvent)
    {
        throw new NotImplementedException("Override this method to add additional cache keys.");
    }
}

有效对比:显式继承类

创建如下显式继承类后,注册代码不变即可正常触发处理器:

public class RefreshTokenUpdatedEventHandler : UpdatedEventHandler<IRefreshTokenRepository, RefreshToken, RefreshTokenId, IRefreshTokenDto, UpdatedEvent<RefreshToken, RefreshTokenId>>
{
    public RefreshTokenUpdatedEventHandler(IRefreshTokenRepository repository) : base(repository) { }
}

解决方案

问题根源是MediatR对嵌套泛型闭包类型的解析支持有限,无法正确识别包含泛型事件参数的处理器类型。以下是三种解决方式:

方式1:使用MediatR批量注册扩展

放弃手动单个注册,改用框架提供的批量扫描注册,让MediatR自动识别所有处理器类型(包括泛型闭包):

// 扫描泛型处理器所在的程序集
collection.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(UpdatedEventHandler<,,,,>).Assembly));

方式2:简化泛型处理器的类型参数

移除事件类型的泛型参数,通过约束自动推导事件类型,降低嵌套泛型复杂度:

public class UpdatedEventHandler<TIRepository, TEntity, TId, TIDto> : IEventHandler<UpdatedEvent<TEntity, TId>> 
    where TIRepository : IRepository<TEntity, TId, TIDto>
    where TEntity : Entity<TId>
    where TId : IdObject<TId>
    where TIDto : IDto<TId>
{
    private readonly TIRepository _repository;

    public UpdatedEventHandler(TIRepository repository)
    {
        _repository = repository;
    }

    public Task Handle(UpdatedEvent<TEntity, TId> updatedEvent, CancellationToken cancellationToken)
    {
        var _cacheKeys = GetBaseCacheKeysAsync(updatedEvent);

        return _repository.ClearCacheAsync(_cacheKeys);
    }

    private async IAsyncEnumerable<string> GetBaseCacheKeysAsync(UpdatedEvent<TEntity, TId> updatedEvent)
    {
        yield return await _repository.EntityValueCacheKeyAsync(nameof(_repository.GetByIdAsync),
            updatedEvent.Updated.Id.Value.ToString());
        yield return await _repository.EntityCacheKeyAsync(nameof(_repository.GetListAsync));
        
        await foreach (var cacheKey in GetCacheKeysAsync(updatedEvent))
        {
            yield return cacheKey;
        }
    }

    protected virtual IAsyncEnumerable<string> GetCacheKeysAsync(UpdatedEvent<TEntity, TId> updatedEvent)
    {
        throw new NotImplementedException("Override this method to add additional cache keys.");
    }
}

注册代码简化为:

collection.AddTransient(
    typeof(IEventHandler<UpdatedEvent<RefreshToken, RefreshTokenId>>),
    typeof(UpdatedEventHandler<IRefreshTokenRepository, RefreshToken, RefreshTokenId, IRefreshTokenDto>));

方式3:保留显式继承类(临时方案)

若不想修改现有代码结构,可继续使用显式继承类的方式,虽然增加了代码量,但能确保MediatR正确识别处理器。


内容的提问来源于stack exchange,提问作者Yannik Köllmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 14:43:32