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.
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);