Search code examples
c#entity-frameworkmef

Entity Framework: dynamically change model based on plugins found with MEF


I am working on an application, which will be extendable via plugins using MEF. The application is using Entity Framework. The context is created by factory method:

public class DataContextFactory
{
    [ImportingConstructor]
    public DataContextFactory([ImportMany] IEnumerable<IModelCreator> modelCreators, [Import(AllowDefault = true)]IDatabaseInitializer<DataContext> initializer)
    {
        ModelCreators = modelCreators;
        Initializer = initializer ?? new DefaultDataContextInitializer();
    }

    public DbContext Create()
    {
        return new DataContext(ModelCreators, Initializer);
    }
}


public interface IModelCreator
{
    void OnModelCreating(DbModelBuilder modelBuilder);
}

public class DataContext : DbContext
{
    public DataContext(IEnumerable<IModelCreator> modelCreators, IDatabaseInitializer<DataContext> initializer)
    {
        ModelCreators = modelCreators;
        if (initializer != null)
        {
            Database.SetInitializer(initializer);
        }
    }

    private IEnumerable<IModelCreator> ModelCreators { get; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

        foreach (var creator in ModelCreators)
        {
            creator.OnModelCreating(modelBuilder);
        }
    }
}

where each plugin defines it's own IModelCreator. Entities will be then accessed by `DbContext.Set().

The problem is that EF won't create additional tables, it will either drop the db or throw exception (depending on initializer used). When I turn on migrations, I am not able to generate migration, because there is no model in project, where the DbContext is.

Will each plugin have to supply SQL script to create it's tables or is there a way for EF to add tables at runtime for entities imported with plugin?

If I implement my own IDatabaseInitializer how can I tell it to create missing tables?


Solution

  • I have used MigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration> Class.

    I have created CompositeDataContextInitializer and changed my DataContextFactory to pass it to my DataContext as argument.

    public class CompositedDataContextInitializer : MigrateDatabaseToLatestVersion<DataContext, Migrations.MigrationConfiguration>
    {
        protected virtual void Seed(DataContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            foreach (var seeder in ServiceLocator.Current.GetAllInstances<IDataSeeder>())
            {
                seeder.Seed(context);
            }
        }
    }
    
    
    [Export(typeof(IDbContextFactory<DbContext>))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class ContextFromCompositionFactory : IDbContextFactory<DbContext>, IDbContextFactory<DataContext>
    {
        public DbContext Create() => new DataContext(ServiceLocator.Current.GetAllInstances<IModelCreator>(), new CompositedDataContextInitializer());
    
        DataContext IDbContextFactory<DataContext>.Create() => (DataContext)Create();
    }
    
    
    public interface IModelCreator
    {
        void OnModelCreating(DbModelBuilder modelBuilder);
    }
    
    public class DataContext : DbContext
    {
        public DataContext(IEnumerable<IModelCreator> modelCreators, IDatabaseInitializer<DataContext> initializer)
        {
            ModelCreators = modelCreators;
            if (initializer != null)
            {
                Database.SetInitializer(initializer);
            }
        }
    
        private IEnumerable<IModelCreator> ModelCreators { get; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
    
            foreach (var creator in ModelCreators)
            {
                creator.OnModelCreating(modelBuilder);
            }
        }
    }
    

    There are still some problems with TPT Inheritance; if you want to store new classes which inherit from classes already in system, use TPH Inheritance.