1
votes

I have an application where I have multiple connection strings for my DbContext implementations. To support this in the consuming classes, I've created an IDbContextProvider interface with a Get method that can provide me with the DbContext instances I need.

I've also got an ICommandHandler thing going, and I'm trying to create a decorator that will call DbContext.SaveChangesAsync() on successful command execution. I'm registering my decorator like this:

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(SaveChangesCommandHandlerDecorator<>));

Now, because I don't want to add a type parameter for the DbContext implementaiton, and I know all classes derived from DbContext have a SaveChangesAsync() method, I figured I could use covariance. So my interface looks like this:

public public interface IDbContextProvider<out TDbContext> where TDbContext : DbContext
{
    TDbContext Get(DbPrivileges privileges);
}

And the relevant part of my decorator:

public class SaveChangesCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> 
    where TCommand : ICommand
{
    private readonly ICommandHandler<TCommand> _handler;
    private readonly IDbContextProvider<DbContext> _dbContextProvider;

    public SaveChangesCommandHandlerDecorator(
        ICommandHandler<TCommand> handler, IDbContextProvider<DbContext> dbContextProvider)
    {
        _handler = handler;
        _dbContextProvider = dbContextProvider;
    }

    ...

However, when I call Verify() on my container, it complains that the IDbContextProvider is invalid, as it's looking for the "base" IDbContextProvider<DbContext> instead of one of the ones registered in my app.

The constructor of type SaveChangesCommandHandlerDecorator<CreateUserCommand> contains the parameter with name 'dbContextProvider' and type IDbContextProvider<DbContext> that is not registered.Please ensure IDbContextProvider<DbContext> is registered, or change the constructor of SaveChangesCommandHandlerDecorator<CreateUserCommand>. Note that there exists a registration for a different type (Redacted).IDbContextProvider<TDbContext> while the requested type is (Redacted).IDbContextProvider<Microsoft.EntityFrameworkCore.DbContext>.

This actually makes sense, since Simple Injector has no way of knowing what concrete type to inject into the dbContextProvider parameter.

Is there any way of customizing the way my decorator is created, so that it can peek at the dependencies of the underlying ICommandHandler implementation's dependencies, and pick the IDbContextProvider signature from there on creation? So if my command handler has a IDbContextProvider<AwesomeDbContext>, I want that to be resolved for my decorator as well.

1

1 Answers

2
votes

So let me get this straight: your application contains multiple DbContext implemtantations, such as:

  • AwesomeDbContext
  • EventBetterDbContext
  • BrilliantDbContext

Now each command handler will typically depend on one particular DbContext implementation, by getting an IDbContextProvider<TDbContext> where the TDbContext is the specific DbContext implementation.

Now depending on what the decorator command handler depends upon, you wish to inject that exact same IDbContextProvider<TDbContext> implementation in the decorator as well, so you can save the changes for that particular instance.

If I summarize your question correctly, the short answer to your question is: no, you can't do that.

The longer answer is, yes this is actually possible by adding an extra generic type argument for TDbContext to your SaveChangesCommandHandlerDecorator<TCommand, TDbContext> decorator, and use the RegisterDecorator overload that accepts the Func<DecoratorPredicateContext, Type>. Using the supplied DecoratorPredicateContext to the factory delegate, you can analyze its Expression property to find out which IDbContextProvider<TDbContext> is injected, and based on that information you build a partially-closed version of SaveChangesCommandHandlerDecorator<TCommand, TDbContext> where you fill in TDbContext but leave TCommand open for Simple Injector to fill in.

But to be honest, I wouldn't take this route. It takes a lot of work, leads to hard to understand code, and your coworkers will hate you for this.

Instead, try injecting a list of IDbContextProvider<TDbContext> instances into the decorator instead. You can do that by making the IDbContextProvider<out TDbContext> registrations both separately, and as part of a collection using RegisterCollection.

When you inject the collection, the decorator can simply call save changes on all the DbContext instances. SaveChanges will be really fast when there are no changes, so from a performance perspective, I don't think there is anything to worry about.