EF code first: inherited dbcontext creates two dat

2019-04-25 07:19发布

I'm trying to create a base dbcontext that contains all the common entities that will always be reused in multiple projects, like pages, users, roles, navigation etc.

In doing so I have a ContextBase class that inherits DbContext and defines all the DbSets that I want. Then I have a Context class that inherits ContextBase where I define project specific DbSets. The classes are defined as follows:

public class ContextBase : DbContext
{
    public virtual DbSet<User> Users { get; set; }
    //more sets

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new UsersConfiguration());
        //add more configurations
    }
}


public class Context : ContextBase
{
    public DbSet<Building> Buildings { get; set; }
    //some more project specific sets

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Configurations.Add(new BuildingsConfiguration());
        //add more project specific configs
    }
}

In my global.asax:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, Configuration>());

where Configuration referes to a class inheriting DbMigrationsConfiguration and overriding the Seed method.

The two context classes are defined in the same namespace, but cross assembly (in order that I may update the base project in multiple existing projects without touching the project specific code) - not sure if this is relevant.

MY PROBLEM: When running this code, it works fine, but when looking in the Database, it actually creates two different databases!! One containing all the base entity tables and one containing BOTH base and custom tables. CRUD operations are only performed on the custom version (which is obviousely what I want), but why does it create the schema of the other one as well?

Any help is appreciated, thanks!

UPDATE:

The following code is what I ended up with. It isn't ideal, but it works. I would still love to get feedback on ways to improve this, but in the meantime I hope this helps further the process. I REALLY DO NOT RECOMMEND DOING THIS! It is extremely error prone and very frustrating to debug. I'm merely posting this to see if there is any better ideas or implementations to achieve this.

One (but not the only) issue still existing is that the MVC views have to be manually added to projects. I've added it to the Nuget package, but it takes 2 to 3 hours to apply a nuget package with so many files when VS is connected to TFS. With some more work and a custom View engine the views can be precompiled (http://blog.davidebbo.com/2011/06/precompile-your-mvc-views-using.html).

The solution is split into the Base Framework projects and the Custom projects (each category includes its own models and repository pattern). The framework projects are packaged up in a Nuget package and then installed in any custom projects allowing the common functionality of any project like user, role and permission management, content management, etc (often referred to as the Boiler Plate) to be easily added to any new projects. This allows any improvements of the boilerplate to be migrated in any existing custom projects.

Custom Database Initializer:

public class MyMigrateDatabaseToLatestVersion : IDatabaseInitializer<Context>
{
    public void InitializeDatabase(Context context)
    {
        //create the base migrator
        var baseConfig = new FrameworkConfiguration();
        var migratorBase = new DbMigrator(baseConfig);
        //create the custom migrator
        var customConfig = new Configuration();
        var migratorCustom = new DbMigrator(customConfig);

        //now I need to check what migrations have not yet been applied
        //and then run them in the correct order
        if (migratorBase.GetPendingMigrations().Count() > 0)
        {
            try
            {
                migratorBase.Update();
            }
            catch (System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException)
            {
                //if an error occured, the seed would not have run, so we run it again.
                baseConfig.RunSeed(context);
            }
        }
        if (migratorCustom.GetPendingMigrations().Count() > 0)
        {
            try
            {
                migratorCustom.Update();
            }
            catch (System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException)
            {
                //if an error occured, the seed would not have run, so we run it again.
                customConfig.RunSeed(context);
            }
        }
    }
}

Framework's DB Migrations Configuration:

public class FrameworkConfiguration: DbMigrationsConfiguration<Repository.ContextBase>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    public void RunSeed(Repository.ContextBase context)
    {
        Seed(context);
    }

    protected override void Seed(Repository.ContextBase context)
    {
        //  This method will be called at every app start so it should use the AddOrUpdate method rather than just Add.

        FrameworkDatabaseSeed.Seed(context);
    }
}

Custom Project's DB Migrations Configuration:

public class Configuration : DbMigrationsConfiguration<Repository.Context>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    public void RunSeed(Repository.Context context)
    {
        Seed(context);
    }

    protected override void Seed(Repository.Context context)
    {
        //  This method will be called at every app start so it should use the AddOrUpdate method rather than just Add.

        CustomDatabaseSeed.Seed(context);
    }
}

The custom DbContext

//nothing special here, simply inherit ContextBase, IContext interface is purely for DI
public class Context : ContextBase, IContext
{
    //Add the custom DBsets, i.e.
    public DbSet<Chart> Charts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        //Assign the model configs, i.e.
        modelBuilder.Configurations.Add(new ChartConfiguration());
    }
}

Framework DbContext:

//again nothing special
public class ContextBase: DbContext
{
    //example DbSet's
    public virtual DbSet<Models.User> Users { get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder);
}

In the global.asax AppStart:

        //first remove the base context initialiser
        Database.SetInitializer<ContextBase>(null);
        //set the inherited context initializer
        Database.SetInitializer(new MyMigrateDatabaseToLatestVersion());

In the web.config:

<connectionStrings>
    <!--put the exact same connection string twice here and name it the same as the base and overridden context. That way they point to the same database. -->
    <add name="Context" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=CMS2013; Integrated Security=SSPI;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient"/>
    <add name="ContextBase" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=CMS2013; Integrated Security=SSPI;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient"/>
</connectionStrings>

2条回答
老娘就宠你
2楼-- · 2019-04-25 07:57

(from the comments)

You're creating ContextBase objects directly, apparently as new T() in a generic method with ContextBase as a generic type argument, so any initialisers for ContextBase also run. To prevent creating ContextBase objects (if it should never be instantiated directly, if the derived context should always be used), you can mark the class as abstract.

查看更多
小情绪 Triste *
3楼-- · 2019-04-25 08:01

Your ContextBase seems to have an initializer as well.. You can remove this by

Database.SetInitializer<ContextBase>(null);
查看更多
登录 后发表回答