Change table name at runtime

后端 未结 7 1890
陌清茗
陌清茗 2020-12-17 03:22

Lets suppose that I have a db table with name Employee and a respective EF 6.0 db-first model.

Getting all rows of table Employee is done through q

相关标签:
7条回答
  • 2020-12-17 04:00

    I don't know if you should do that, but I think you can. You will have to dig into Entity Framework metadata structures, like MetadataWorkspace, which you can get from the underlying ObjectContext. See an example here: http://weblogs.asp.net/ricardoperes/entity-framework-metadata.

    0 讨论(0)
  • 2020-12-17 04:04

    I know it's been been a while since the original post, but I'll add my answer to help someone else. I had generic SQL queue tables with different table names. I.e. the schema is exactly the same for both tables. I created a framework so that you can dynamically poll the table of your choice by providing the name and that's why I needed to update the table name at run-time. Basically, you can create an interceptor to intercept the raw SQL queries from entity framework and update the table name from there.

    public class MyInterceptor : IDbCommandInterceptor
    {
        private const string TableReplaceString = "[TheTableNameToReplace]";
    
        private void ReplaceTableName(DbCommand command, IEnumerable<DbContext> contexts)
        {
            var myContext = contexts?.FirstOrDefault(x => x is MyContext) as MyContext;
            if (myContext != null && command != null && command.CommandText.Contains(TableReplaceString))
            {
                command.CommandText = command.CommandText.Replace(TableReplaceString, $"[{myContext.NewTableName}]");
            }
        }
    
        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            ReplaceTableName(command, interceptionContext.DbContexts);
        }
    
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            ReplaceTableName(command, interceptionContext.DbContexts);
        }
    
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            ReplaceTableName(command, interceptionContext.DbContexts);
        }
    
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            ReplaceTableName(command, interceptionContext.DbContexts);
        }
    
        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            ReplaceTableName(command, interceptionContext.DbContexts);
        }
    
        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            ReplaceTableName(command, interceptionContext.DbContexts);
        }
    }
    

    Of course, you have to get the new table name from somewhere. Either from the constructor or from a stored field in your custom DBContext which you can grab from interceptionContext.DbContexts.

    Then you just have to register the interceptor for your context.

    public class MyContext : DBContext
    {
        public readonly string NewTableName;
    
        public MyContext(string connectionString, string newTableName)
            : base(connectionString)
        {
            NewTableName = newTableName;
            // Set interceptor
            DbInterception.Add(new MyInterceptor());
        }
    }
    

    UPDATE: I found that if you add the interceptor in the constructor above will cause memory leaks. DotMemory doesn't tell you about this. Make sure you add the interceptor in a static constructor instead.

    public class MyContext : DBContext
    {
        public readonly string NewTableName;
    
        static MyContext()
        {
            // Set interceptor only in static constructor
            DbInterception.Add(new MyInterceptor());
        }
    
        public MyContext(string connectionString, string newTableName)
            : base(connectionString)
        {
            NewTableName = newTableName;
        }
    }
    
    0 讨论(0)
  • 2020-12-17 04:09

    You must create a new dbcontext that inherits from the db-first model context and treat it like code-first in ef. Check the link, please. Same problem as yours.

    https://www.codeproject.com/Articles/421643/How-to-Use-MVC-Net-on-the-Dynamics-NAV-Database-St#_articleTop

    So when mapping, you can get the table name dynamically.

    0 讨论(0)
  • 2020-12-17 04:12

    Old question, but based on the problem I would suggest that you look at partioning your table based on a "current" bit or a datetime field. Partitioning is based on a column value & is supported by most modern DBMS. It would avoid issue at the ORM level.

    0 讨论(0)
  • 2020-12-17 04:16

    Why not use some good old fashioned polymorphism?

    partial class Employee : IEmployee { }
    partial class HistoricalEmployee : IEmployee { }
    
    interface IEmployee {
        public string Name { get; set; }
    }
    
    void PrintEmployeeName(IEmployee employee)
    {
        Debug.WriteLine(employee.Name);
    }
    
    PrintEmployeeName(context.Employees.First());
    PrintEmployeeName(context.HistoricalEmployees.First());
    
    0 讨论(0)
  • 2020-12-17 04:23

    Thanks for the answers.

    I think that my case is a real-world scenario that is, typically neglected in all "getting-started" typical scenarios of EF tutorials and examples.

    Based on the fact that I use db-first approach and the switch should be at the application level, I think that I will create a Context instance, based on different SSDL with the new table name that the user will use on demand

    0 讨论(0)
提交回复
热议问题