Entity Framework map model class to table at run time

asp.net-core-2.0 c# entity-framework-core


In a project using ASP.NET Core 2.0 and Entity Framework, I'm trying to map a known table schema (coded into class MyTableClass) to an unknown table name. This table name is given by the user at run time, so this is done outside of the OnModelCreating method of the Context class. Is there a way to do something like the following pseudocode:

void OnUserEnteredTableNameFromUI(string tableName)
    var modelBuilder = new ModelBuilder(???);  // how?
    // how to get a ref to DbSet<MyTableClass> myTable from here?
8/17/2018 4:18:48 PM

Accepted Answer

I've seen situations where databases with identical structure but varying table names had been deployed to several sites. In that case, EF only needs the know the table name(s) at application startup.

This can be done by adding a constructor parameter to the context:

private readonly string _userDefinedTableName;

public MyContext(string userDefinedTableName)
    _userDefinedTableName = userDefinedTableName;

Then, in OnModelCreating:


However, in your case the name has to change any number of times at runtime. With Entity Framework, that's impossible (well, more exactly, too impractical to really contemplate it). EF compiles and stores model once per context class, because it would be too expensive to do all that for each context instantiation.

That means that OnModelCreating runs not more than once in an application and the first table name remains.

You'll have to find other ways to address table data dynamically, or change the design so the multiple tables can be converted into one fixed table.

8/16/2018 9:13:34 PM

Popular Answer

Since this is an interesting issue which might help other people that need some dynamic model building, here is how it can be implemented.

Let say we have a custom context with custom table name provided via constructor (as Gert Arnold suggested in the other answer):

public class CustomDbContext : DbContext
    // …

    private string customTableName;
    public string CustomTableName => customTableName ?? "DefaultCustomTableName";

and we use it inside the OnModelCreating (it should be there, currently there is no other simple way to create model using the predefined convention sets):


The only problem is that by default the OnModelCreating is called just once per context type and is cached. Luckily EF Core is built on top of a (replaceable) services architecture. The service interface responsible for model caching is IModelCacheKeyFactory:

Creates keys that uniquely identifies the model for a given context. This is used to store and lookup a cached model for a given context.

It has a single method

object Create(DbContext context)

The returned object GetHashCode / Equals methods are used to identify the passed context instance. The default EF Core service implementation returns an object which compares the type of the context.

In order to make the custom context model working, we need to replace it with a custom service which also compares the custom state (CustomTableName in our case). The implementation could be like this (using C#7.0 value tuples):

class CustomModelCacheKeyFactory : IModelCacheKeyFactory
    public object Create(DbContext context) => new CustomModelCacheKey(context);

class CustomModelCacheKey
    (Type ContextType, string CustomTableName) key;
    public CustomModelCacheKey(DbContext context)
        key.ContextType = context.GetType();
        key.CustomTableName = (context as CustomDbContext)?.CustomTableName;
    public override int GetHashCode() => key.GetHashCode();
    public override bool Equals(object obj) => obj is CustomModelCacheKey other && key.Equals(other.key);

The only thing remaining is to replace the existing service with the custom. It can be done inside OnConfiguring override:

optionsBuilder.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory>();

And that's all. Anytime you create context with different CustomTableName, EF Core will create a new model and map the CustomEntity to that table.

The same technique can be applied to any context containing custom model affecting state by including all custom state in CustomModelCacheKey.key tuple. Of course it could be implemented w/o value tuples, just with them the GetHashCode and Equals overrides are easier to implement. Actually instead of CustomModelCacheKey the custom service can return directly value tuple containing the context type and custom state member values.

Related Questions


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow