实体框架在运行时将模型类映射到表

时间:2018-08-15 17:58:47

标签: c# entity-framework-core asp.net-core-2.0

在使用ASP.NET Core 2.0和Entity Framework的项目中,我试图将已知的表架构(编码为类MyTableClass)映射到未知的表名称。该表名由用户在运行时指定,因此可以在Context类的OnModelCreating方法之外进行。有没有办法执行以下伪代码:

void OnUserEnteredTableNameFromUI(string tableName)
{
    var modelBuilder = new ModelBuilder(???);  // how?
    modelBuilder.Entity<MyTableClass>().ToTable(tableName);
    // how to get a ref to DbSet<MyTableClass> myTable from here?
}

2 个答案:

答案 0 :(得分:1)

 

我见过这样的情况,其中具有相同结构但表名不同的数据库已部署到多个站点。在这种情况下,EF仅需要在应用程序启动时知道表名。

这可以通过向上下文添加构造函数参数来完成:

private readonly string _userDefinedTableName;

public MyContext(string userDefinedTableName)
{
    _userDefinedTableName = userDefinedTableName;
}

然后,在OnModelCreating中:

modelBuilder.Entity<MyTableClass>().ToTable(_userDefinedTableName);

但是,在您的情况下,名称必须在运行时更改任何次数。使用实体框架,这是不可能的(确切地说,太不切实际了,无法进行实际考虑)。 EF对每个上下文类仅一次编译并存储模型,因为对于每个上下文实例进行所有操作都太昂贵了。

这意味着OnModelCreating在一个应用程序中运行不超过一次,并且第一个表名仍然保留。

您将必须找到其他方法来动态处理表数据或更改设计,以便可以将多个表转换为一个固定表。

答案 1 :(得分:0)

由于这是一个有趣的问题,可能会帮助需要动态模型构建的其他人,因此可以通过以下方式实现它。

假设我们有一个通过构造函数提供的具有自定义表名的自定义上下文(如Gert Arnold在另一个答案中建议的那样):

public class CustomDbContext : DbContext
{
    // …

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

,我们在OnModelCreating内部使用它(应该在那里,目前没有其他简单的方法可以使用预定义的约定集创建模型):

modelBuilder.Entity<CustomEntity>().ToTable(CustomTableName); 

唯一的问题是,默认情况下,OnModelCreating在每种上下文类型中仅被调用一次并被缓存。幸运的是,EF Core是建立在(可替换的)服务体系结构之上的。负责模型缓存的服务接口为IModelCacheKeyFactory

  

创建用于在给定上下文中唯一标识模型的键。这用于存储和查找给定上下文的缓存模型。

它只有一个方法

object Create(DbContext context)

返回的对象GetHashCode / Equals方法用于标识传递的上下文实例。默认的EF Core服务实现返回一个对象,该对象比较上下文的类型。

为了使自定义上下文模型正常工作,我们需要用一个自定义服务替换它,该服务还比较自定义状态(在本例中为CustomTableName)。实现可能是这样的(使用C#7.0值元组):

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

剩下的唯一事情就是用自定义替换现有服务。可以在OnConfiguring覆盖内完成

optionsBuilder.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory>();

仅此而已。每当您使用不同的CustomTableName创建上下文时,EF Core都会创建一个新模型并将CustomEntity映射到该表。

通过在CustomModelCacheKey.key元组中包含所有自定义状态,可以将相同的技术应用于包含影响状态的自定义模型的任何上下文。当然,它可以不使用值元组来实现,只需使用GetHashCodeEquals覆盖即可实现。实际上,自定义服务可以代替包含CustomModelCacheKey的值元组直接返回包含上下文类型和自定义状态成员值的元组。