Search code examples
c#asp.netasp.net-mvcentity-frameworksimple-injector

Can DbContext be composed instead of inherited?


Let's say I am doing a code-first development for a school and I have a SchoolDbContext. Most documentation on Entity Framework suggest you derive from DbContext:

public class SchoolDbContext : DbContext
{
    public IDbSet<Student> Students => Set<Student>();
}

But my argument is SchoolDbContext is never a specialisation of DbContext, it is instead just making use of DbContext so in my opinion, SchoolDbContext should be composed of DbContext:

public class SchoolDbContext
{
    private readonly DbContext _dbContext;

    public SchoolDbContext(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IDbSet<Student> Students => _dbContext.Set<Student>();
}

In my composition root of an ASP.NET MVC application, I tried this composition approach by setting up my dependencies like this (using Simple Injector as an example):

private static void RegisterDependencies(Container container)
{
     // Assume I have a connection string SchoolDbContext
     container.Register(() => new DbContext("SchoolDbContext"), Lifestyle.Scoped);
     container.Register<SchoolDbContext>(Lifestyle.Scoped);
}

Web.config:

<connectionStrings>
    <add name="SchoolDbContext"
         providerName="System.Data.SqlClient"
         connectionString="Server=(localdb)\MSSQLLocalDB;Integrated Security=true"/>
</connectionStrings>

This fails when I try to load Students with:

The entity type Student is not part of the model for the current context.

When I change it back to the inheritance approach (i.e. invoking the default constructor new SchoolDbContext()) everything works perfectly.

Is my composition approach not supported by Entity Framework?


Solution

  • Can DbContext be composed instead of inherited?

    Short answer: NO

    To quote the remarks from official documentation (emphasis mine)

    DbContext is usually used with a derived type that contains DbSet<TEntity> properties for the root entities of the model. These sets are automatically initialized when the instance of the derived class is created....

    There is more but it is too much to put in this answer.

    Source DbContext Class

    Is my composition approach not supported by Entity Framework?

    If you look at the source code for DbContext there are internal methods that search the class for DbSets and initializes them.

    Mainly this section of code

    /// <summary>
    ///     Initializes the internal context, discovers and initializes sets, and initializes from a model if one is provided.
    /// </summary>
    private void InitializeLazyInternalContext(IInternalConnection internalConnection, DbCompiledModel model = null)
    {
        DbConfigurationManager.Instance.EnsureLoadedForContext(GetType());
    
        _internalContext = new LazyInternalContext(
                this, internalConnection, model
                , DbConfiguration.GetService<IDbModelCacheKeyFactory>()
                , DbConfiguration.GetService<AttributeProvider>());
        DiscoverAndInitializeSets();
    }
    
    /// <summary>
    ///     Discovers DbSets and initializes them.
    /// </summary>
    private void DiscoverAndInitializeSets()
    {
        new DbSetDiscoveryService(this).InitializeSets();
    }
    

    Any model you try to get will throw the same error as the model was not part of the context when it was initialized which can only happen if it was a member of a derived class.

    DbContext source code on Github