0
votes

I have looked at several similar questions here but nothing has helped so far.

I am trying to dynamically create a DbContext based on a type that isn't known until runtime. The DbContext is in another library that I reference in my app. I need to pass a DbContextOptions object to the DbContext's constructor. SO I am creating a DbContextOptionsBuilder and trying to call the UseSqlServer() method passing in a connection string. However I get the error in the title.

Other similar questions always say to add the packages Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer and I have done that but without success.

Here is my code right now:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

private DbContext GetDbContext()
{
    Console.WriteLine("\nGetting DB Context...");

    Assembly LaytonDBSets = Assembly.Load("LaytonDBSets.Core");

    List<TypeInfo> dbContexts = LaytonDBSets.DefinedTypes.Where(t => typeof(DbContext).IsAssignableFrom(t)).ToList();

    foreach (TypeInfo ti in dbContexts)
    {
        Type ctxType = LaytonDBSets.GetType(ti.FullName);

        // Get the DbContextOptions to pass to the DbContext constructor
        Type dbContextOptionsBuilderBase = typeof(DbContextOptionsBuilder<>);
        Type dbContextOptionsBuilderType = dbContextOptionsBuilderBase.MakeGenericType(ctxType);
        dynamic dbContextOptionsBuilder = Activator.CreateInstance(dbContextOptionsBuilderType);
        string connStr = iconfig.GetConnectionString(ti.Name);
        dbContextOptionsBuilder.UseSqlServer(connStr); // ERROR HERE
        var options = dbContextOptionsBuilder.Options;

        dynamic ctx = Activator.CreateInstance(ctxType, args: new object[] { options });

        // stuff to be added...
    }

    // stuff to be added...
}

If there is a batter way to do what I am attempting please let me know, I have never done anything like this before.

1
My guess is that the extension method won't bind to the dynamic dbContextOptionsBuilder instance as it doesn't know the type information to bind to it to be able to call the UseSqlServer() method. - Martin Costello
@MartinCostello I could be wrong, but this is the error message I get: "Microsoft.EntityFrameworkCore.DbContextOptionsBuilder<LaytonDBSets.Core.LayTimeDB>' does not contain a definition for 'UseSqlServer'" ; doesn't that mean that it does know the type it is trying to bind to? - Mr. Spock

1 Answers

1
votes

This is the helper class to do that:

public class ContextHelper
{
    public static DbContext CreateDbContext<T>(string connectionString) where T : DbContext
    {
        var optionsBuilder = new DbContextOptionsBuilder<T>();
        optionsBuilder.UseSqlServer(connectionString);

        var options = optionsBuilder.Options;
        var context = (DbContext)Activator.CreateInstance(typeof(T), options);

        return context;
    }

    private static MethodInfo _createDbContext = typeof(ContextHelper).GetMethod("CreateDbContext", new Type[]{typeof(string)});

    public static List<DbContext> CreateDbContexts(IEnumerable<(Type Type, string ConnectionString)> contextTypes)
    {
        var result = new List<DbContext>();
        foreach (var contextType in contextTypes)
        {
            result.Add((DbContext)_createDbContext.MakeGenericMethod(contextType.Type)
                .Invoke(null, new[] {contextType.ConnectionString}));
        }

        return result;
    }
}

Usage in your case:

var typeWithConnectionString = 
    dbContexts.Select(ti => (LaytonDBSets.GetType(ti.FullName), iconfig.GetConnectionString(ti.Name)));

var contexts = ContextHelper.CreateDbContexts(typeWithConnectionString);