我正在尝试创建一个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个参数
1条答案
按热度按时间tzcvj98z1#
一个建议的替代方案而不是直接的答案。
如果您没有从处理程序中获取DbContext,而是将其注入到管道中,该怎么办?
您仍然可以检查bool的抽象类/接口,以确定是否需要保存,但是您可以摆脱处理程序和dbcontext之间的依赖关系。
我看到的唯一问题是,现在您只使用了一个dbcontext,这很可能是大多数项目的情况。
如果要使用多个DBcontext,则必须注入一个工厂,而不是MyDbContext。
该工厂必须通过服务提供者获取DbContext,如下所示
x一个一个一个一个x一个一个二个x