.net MediatR管道,在每个命令之后保存通用DbContext

e0bqpujr  于 2023-01-10  发布在  .NET
关注(0)|答案(1)|浏览(116)

我正在尝试创建一个MediatR管道,它在每个命令之后调用DbContext上的SaveChangesAsync。我正在尝试以通用方式实现这一点,这样我就可以使用DI添加管道,并且它将对我的所有命令都有效。默认情况下,我希望保存它,但我希望在命令中具有设置布尔值的选项,如果将其设置为false,我不想在我的DbContext上调用SaveChangesAsync。我基本上已经找到了一个可行的解决方案,但是在我的DI注册中,我必须指定我正在使用哪个DbContext,我想阻止它。
我写了下面的代码:保存命令行为:

public class SaveCommandBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IRequestHandler<TRequest, TResponse> _requestHandler;

    public SaveCommandBehavior(IRequestHandler<TRequest, TResponse> requestHandler)
    {
        _requestHandler = requestHandler;
    }
    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var response = await next();

        var isDbCommandHandler = IsAssignableToGenericType(_requestHandler.GetType(), typeof(DbCommandHandler<>));

        if (isDbCommandHandler &&
            _requestHandler is DbCommandHandler<MyDbContext> commandHandler &&
            request is DbCommand { SaveChanges: true } or DbCommand<TResponse> { SaveChanges: true })
        {
                await commandHandler.DbContext.SaveChangesAsync(cancellationToken);
        }

        return response;
    }

    public static bool IsAssignableToGenericType(Type givenType, Type genericType)
    {
        var interfaceTypes = givenType.GetInterfaces();

        foreach (var it in interfaceTypes)
        {
            if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
                return true;
        }

        if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
            return true;

        Type baseType = givenType.BaseType;
        if (baseType == null) return false;

        return IsAssignableToGenericType(baseType, genericType);
    }
}

注册:

public static IServiceCollection AddSaveCommandPipelineLocal(this IServiceCollection services)
    {
        return services.AddTransient(typeof(IPipelineBehavior<,>), typeof(SaveCommandBehavior<,>));
    }

我在命令中使用的抽象类:

public abstract class DbCommand: ICommand 
    {
        public bool SaveChanges { get; set; } = true;
    }
    public abstract class DbCommand<TResponse> : ICommand<TResponse>
    {
        public bool SaveChanges { get; set; } = true;
    }

    public abstract class DbCommandHandler<TDbContext> where TDbContext : DbContext
    {
        public TDbContext DbContext;

        protected DbCommandHandler(TDbContext dbContext)
        {
            DbContext = dbContext;
        }
    }

示例命令和处理程序:

public sealed class MyCommand: DbCommand
{
    public Guid Parameter1  { get; set; }
}

public class MyCommandHandler: DbCommandHandler<MyDbContext>, ICommandHandler<MyCommand>
{

    public MyCommandHandler(MyDbContext dbContext)
        : base(dbContext)
    {
      
    }
    public async Task<Unit> Handle(MyCommand request, CancellationToken cancellationToken)
    {
        // Command logic making changes to EF, without calling save.
    }
}

虽然这段代码可以工作,但我必须使用_requestHandler是我不想要的DbCommandHandler命令处理程序:此时,我已经知道我正在处理的_requestHandler应该具有基本类型为"DbContext"的DbContext,这些信息应该足以对其调用"SaveChangesAsync"。但是,如果不指定正在处理的DbContext,我无法确定如何获取该类型。我能够确定它是否是具有任何类型的DbContext的有效类型。使用isDbCommandHandler变量,但我不能利用它将其转换为一个可用的变量,我可以使用它的DbContext。
我觉得我忽略了一些简单的东西。我觉得我应该在SaveCommandBehavior中添加一个参数TContext,其中TContext:DbContext,所以我可以在强制转换中使用它,但是我的SaveCommandBehavior中有一个额外的泛型参数,这会使我的DI失败,因为PipelineBehavior只接受2个参数

tzcvj98z

tzcvj98z1#

一个建议的替代方案而不是直接的答案。
如果您没有从处理程序中获取DbContext,而是将其注入到管道中,该怎么办?
您仍然可以检查bool的抽象类/接口,以确定是否需要保存,但是您可以摆脱处理程序和dbcontext之间的依赖关系。

public class SaveCommandBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IRequestHandler<TRequest, TResponse> _requestHandler;
    private readonly MyDbContext _myDbContext;

    public SaveCommandBehavior(
MyDbContext myDbContext,
IRequestHandler<TRequest, TResponse> requestHandler)
    {
        _requestHandler = requestHandler;
    }
    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var response = await next();

        var isDbCommandHandler = IsAssignableToGenericType(_requestHandler.GetType(), typeof(DbCommandHandler<>));

        if (isDbCommandHandler &&
            request is DbCommand { SaveChanges: true } or DbCommand<TResponse> { SaveChanges: true })
        {
                await _myDbContext.SaveChangesAsync(cancellationToken);
        }

        return response;
    }
...
}

我看到的唯一问题是,现在您只使用了一个dbcontext,这很可能是大多数项目的情况。
如果要使用多个DBcontext,则必须注入一个工厂,而不是MyDbContext。
该工厂必须通过服务提供者获取DbContext,如下所示
x一个一个一个一个x一个一个二个x

相关问题