efcore: Inheritance problem with DbContext subclasses requiring constructor to supply DbContextOptions

I am building web APIs in ASP.NET Core 1.1.

I have a number different databases (for different systems) which have common base schemas for configuration items such as Configuration, Users and groups (about 25 tables in all). I am trying to avoid duplicating the quite extensive EF model configuration for the shared part of the model by inheriting from a base class.

     InvestContext     BatchContext
             ^               ^
             |               |
             _________________
                     |      
            ConfigurationContext
                     ^
                     |      
                 DbContext

However, this does not work because of the Entity Framework (EF) requirement to pass DbContextOptions<DerivedContext> as a parameter to the constructor, where DerivedContext must match the type of the context the constructor is called on. The parameter must then be passed down to the base DbContext by calling :base(param).

So when (for example) InvestContext is initialised with DbContextOptions<InvestContext>, it calls base(DbContextOptions<InvestContext>) and EF throws an error because the call to the ConfigurationContext constructor is receiving a parameter of type DbContextOptions<InvestContext> instead of the required type DbContextOptions<ConfigurationContext>.

I can work around this by building a new DbContextOptions<ConfigurationContext> from the DbContextOptions<InvestContext> object and passing that to base, but it seems like a hack and results in DbContext being initialised with DbContextOptions<ConfigurationContext> for any context which inherits from ConfigurationContext.

So I guess I’m asking:

  1. Is that hack I’ve described a problem, or can I use it (ie. will there be unexpected effect of DbContext receiving DbContextOptions<ConfigurationContext> when InvestContext is constructed?
  2. Is this limitation on inheritance intended, or just a side effect of the design choice requiring DbContextOptions<DerivedContext>
  3. Is there perhaps a better way to do this which would allow for a clean inheritance model?

Thanks, and thanks for all the good work - Peter

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 2
  • Comments: 28 (12 by maintainers)

Commits related to this issue

Most upvoted comments

I was able to resolve this without a hack by providing a protected constructor that uses DbContextOptions without any type. Making the second constructor protected ensures that it will not get used by DI.

public class MainDbContext : DbContext
{
    public MainDbContext(DbContextOptions<MainDbContext> options)
        : base(options)
    {
    }

    protected MainDbContext(DbContextOptions options)
        : base(options)
    {
    }
}

public class SubDbContext : MainDbContext
{
    public SubDbContext (DbContextOptions<SubDbContext> options)
        : base(options)
    {
    }
}

@ajcvickers What is the specific reason that DbContextOptions<T> is required for the derived class but the base only receives DbContextOptions?

@Ettery I have been trying to reproduce this but have not been able to. The code already allows the TContext generic type to be of a derived DbContext type–see test code below. Can you post the exact exception message and stack trace you are seeing?

public class Program
{
    public static void Main()
    {
        var appServiceProivder = new ServiceCollection()
            .AddDbContext<DerivedContext2>()
            .BuildServiceProvider();

        using (var serviceScope = appServiceProivder
            .GetRequiredService<IServiceScopeFactory>()
            .CreateScope())
        {
            var context = serviceScope.ServiceProvider.GetService<DerivedContext2>();

            Debug.Assert(context.GetService<IDbContextOptions>().GetType() == typeof(DbContextOptions<DerivedContext2>));
        }
    }
}

public class DerivedContext1 : DbContext
{
    public DerivedContext1(DbContextOptions options)
        : base(options)
    {
    }
}

public class DerivedContext2 : DerivedContext1
{
    public DerivedContext2(DbContextOptions<DerivedContext2> options)
        : base(options)
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseInMemoryDatabase(nameof(DerivedContext2));
}