Search code examples
c#entity-frameworkef-code-first

EF manual/dynamic table registration


I'm trying to create some tables manually and map to DbSet dynamically but it does not work. Here is my DbContext class:

public abstract class DynamicDbContext : DbContext
{
    private List<IQueryable> m_dbSets;

    public DynamicDbContext([NotNull] DbContextOptions options) :
        base(options)
    {
    }

    public DbSet<T> GetTbl<T>() where T : class
    {
        if (m_dbSets == null)
        {
            m_dbSets = new List<IQueryable>();

            foreach (var iter in GetDynTypes())
            {
                var setMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set), new[] { typeof(string) });
                IQueryable foundSet = (IQueryable)setMethod
                    .MakeGenericMethod(iter)
                    .Invoke(this, new object[] { iter.Name });

                    m_dbSets.Add(foundSet);
                }
            }
        }

        return m_dbSets.FirstOrDefault(obj => obj.ElementType == typeof(T)) as DbSet<T>;
    }
    
    protected abstract List<Type> GetDynTypes();

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

        foreach (var iter in GetDynTypes())
        {
            EntityTypeBuilder builder = modelBuilder.Entity(iter);
            builder.ToTable(iter.Name);
        }
    }
    #endregion
}

The class obtains the dynamic types from application's derived class and should register the classes as tables to database. Then I would like to use Linq to work with the tables, so I introduced GetTbl method which would return the DbSet<T> so the application can call Linq on this set. This method use reflection to obtain the DbSet, but I also tried directly call Set<T>(typeof(T).Name).

When I try to access the returned DbSet the result is exception: Cannot create a DbSet for 'Abc' because this type is not included in the model for the context

It looks like the table(s) are created e.g. when I run migration command I can see the tables in the database:

dotnet ef migrations add InitialCreate

The problem is probably with the table mapping to DbSet - in my GetTbl method. Or maybe I need to do something extra in OnModelCreating method.

Not sure what is the problem. Any help is appreciated :-) Thanks


Solution

  • Once you register the types in OnModelCreating, you can get the DbSet's with simply

     myDbContext.Set<T>();
    

    And so a simpler design is to just use a single generic type that you specialize at runtime with the dynamic entity type. eg

    public class DynamicDbContext<T> : DbContext where T:class
    {
        public DynamicDbContext([NotNull] DbContextOptions options) :
            base(options)
        {
        }
    
        public DbSet<T> Entities => this.Set<T>();
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<T>().ToTable(typeof(T).Name);
            base.OnModelCreating(modelBuilder);
        }
    }
    

    Of course this won't create the table if it doesn't exist.