Search code examples
c#asp.net-coredependency-injectionentity-framework-coredbcontext

Inject IDbContextFactory<ApplicationDbContext> in a constructor waiting for IDbContextFactory<BaseDbContext>


I'm developing an Asp.Net Core Library intended for use across various microservices within my environment.

The primary goal of this library is to standardize the code used for database access. I've therefore created a BaseDbContext class that implements EntityFramework's DbContext class.

Subsequently, different developers and I are supposed to implement my BaseDbContext within our own ApplicationDbContext.

In our environment, we have multiple databases, say one per user.

For each user request, we need to find their specific database and then dynamically construct a DbContext.

To accomplish this, I am looking to register a DbContextFactory that will create an ApplicationDbContext for each request, fetching the corresponding ConnectionString through a method called RetrieveConnectionString.

So far, it's pretty straightforward and looks like this:

public static WebApplicationBuilder AddApplicationDbContextFactory<TApplicationDbContext>(this WebApplicationBuilder builder) where TApplicationDbContext : BaseDbContext
{
    builder.Services.AddDbContextFactory<TApplicationDbContext>((serviceProvider, options) =>
    {
        builder.ConfigureDbContextOptions(options);
    });
    return builder;
}

The ConfigureDbContextOptions method calls the previously mentioned RetrieveConnectionString.

My issue is that within the same library, I also have various classes that already use DbContext, such as Repository or an UnitOfWork.

The ApplicationDbContext does not exist when writing the library's code, so the code relies on the BaseDbContext class:

public class UnitOfWork(IDbContextFactory<BaseDbContext> dbContextFactory) : IUnitOfWork
{
    protected bool _disposed;
    private readonly BaseDbContext _dbContext = dbContextFactory.CreateDbContext();
}

My problem is that even though our future ApplicationDbContext classes implement the BaseDbContext, and I register IDbContextFactory<ApplicationDbContext>, when the Repository and UnitOfWork classes try to retrieve an instance of IDbContextFactory<BaseDbContext>, I receive an error because I haven't registered IDbContextFactory<BaseDbContext>.

I tried adding this to my method:

public static WebApplicationBuilder AddApplicationDbContextFactory<TApplicationDbContext>(this WebApplicationBuilder builder) where TApplicationDbContext : BaseDbContext
{
    builder.Services.AddDbContextFactory<TApplicationDbContext>((serviceProvider, options) =>
    {
        builder.ConfigureDbContextOptions(options);
    });

    builder.Services.AddScoped(typeof(IDbContextFactory<BaseDbContext>), provider =>
    {
        return provider.CreateScope().ServiceProvider.GetRequiredService<IDbContextFactory<TApplicationDbContext>>();
    });

    return builder;
}

But I obviously get the following error:

'Object of type 'Microsoft.EntityFrameworkCore.Internal.DbContextFactory[ApplicationDbContext]' cannot be converted to type 'Microsoft.EntityFrameworkCore.IDbContextFactory[BaseDbContext]'.'

How can I make it so that my Repository and UnitOfWork can use a BaseDbContext when registering an ApplicationDbContext?

UPDATE 1 :

As pointed out in the comments, the error relates to conversion, not casting. It originates from a DAO which was implementing my Repository class.

I modified the code as follows:

builder.Services.AddScoped(typeof(IDbContextFactory<BaseDbContext>), provider =>
{
    return (IDbContextFactory<BaseDbContext>)provider.CreateScope().ServiceProvider.GetRequiredService<IDbContextFactory<TApplicationDbContext>>();
});

Now, I encounter a casting error:

System.InvalidCastException : 'Unable to cast object of type 'Microsoft.EntityFrameworkCore.Internal.DbContextFactory[ApplicationDbContext]' to type 'Microsoft.EntityFrameworkCore.IDbContextFactory[BaseDbContext]'.'

This is somewhat surprising since the TContext parameter of IDbContextFactory<> is covariant.

Also, it might be worth noting that it's attempting to cast an Internal.DbContextFactory to an IDbContextFactory.


Solution

  • IDbContextFactory is not covariant, you cannot cast IDbContextFactory to IDbContextFactory even if ApplicationDbContext derives from BaseDbContext.

    To resolve this issue ypou can create an adapter class DbContextFactoryAdapter that implements IDbContextFactory and wraps an instance of IDbContextFactory. Modify your DI registration to use this adapter, allowing services expecting a BaseDbContext factory to receive an adapted version of the specific context factory.