Search code examples
c#entity-framework-coreasp.net-core-2.0system.reflectionef-core-2.0

Load EF Core 2 DbContext from dynamically imported DLL


I have a DbContext I need to dynamically loading from a DLL as the Db context will change and need to be edited while the application is running, however I can't seem to create an instance of this context as I appear to need to pass in the context options. this code is below.

Assembly reporting = Assembly.LoadFile(pathString);

        var defaultContext = reporting.GetType("Reporting.DefaultContext");
        var dbContext = Activator.CreateInstance(defaultContext, new
 object[] { /*need to pass in the contructor here*/ }) as DbContext;

and my compiled context is pretty standard, a modified version of this below for reference

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

    public virtual DbSet<Student> Students { get; set; }
    public virtual DbSet<Course> Courses { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>().ToTable("Students ", "core")
            .AllProperties().ForEach(x => x.IsRequired());

        modelBuilder.Entity<Course>().ToTable("Courses", "core")
            .AllProperties().ForEach(x => x.IsRequired());
    }
}

I'm pretty inexperienced when it comes to reflection - so questions really are;

Is this the best way to try and achieve this or should I be creating a method to invoke on the DLL to return the context?

If not how do I go about passing through an instance of DbContextOptions when I don't have the context?

Side note, this is an Asp.Net Core 2.0 MVC project.

EDIT: when instead passing an empty obj array into create instance as below I get a missing method exception (constructor not found)

var dbContext = Activator.CreateInstance(defaultContext, new object[] { new object[0] }) as DbContext;

Whereas if I don't pass in the second obj parameter I get the same exception but with message "No parameterless constructor defined for this object" so now I'm pretty lost as there's clearly a construtor.


Solution

  • Basically the question is how to create / get DbContextOptions<DefaultContext> instance via reflection. You can do that by first constructing a generic type via MakeGenericType and then use that type as argument to Activator.CreateInstance.

    So having

    var dbContextType = reporting.GetType("Reporting.DefaultContext");
    

    something like this:

    var optionsType = typeof(DbContextOptions<>).MakeGenericType(dbContextType);
    var options = (DbContextOptions)Activator.CreateInstance(optionsType);
    var dbContext = (DbContext)Activator.CreateInstance(dbContextType, options);
    

    However, the whole purpose of the DbContextOptions<TDbContext> options constructor argument is to allow configuring the db context (like database provider, connection string etc.) from outside, rather than self-configuring inside OnModelConfiguring, in which case your derived db context would simply have parameterless constructor.

    Hence it would be better to create DbContextOptionsBuilder<DefaultContext instance, use Fluent APi to configure it, and then pass Options property (which is the intended DbContextOptions<DefaultContext type) to the DefaultContext constructor. E.g.

    var optionsBuilderType = typeof(DbContextOptionsBuilder<>).MakeGenericType(dbContextType);
    var optionsBuilder = (DbContextOptionsBuilder)Activator.CreateInstance(optionsBuilderType);
    
    string connectionString = ...;
    optionsBuilder.UseSqlServer(connectionString);
    
    var dbContext = (DbContext)Activator.CreateInstance(dbContextType, optionsBuilder.Options);