【问题标题】:Entity Framework map model class to table at run time实体框架在运行时将模型类映射到表
【发布时间】:2019-01-22 15:28:04
【问题描述】:

在一个使用 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?
}

【问题讨论】:

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


    【解决方案1】:

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

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

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

    然后,在OnModelCreating:

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

    但是,在您的情况下,名称必须在运行时更改任意次数。使用实体框架,这是不可能的(嗯,更准确地说,太不切实际了,无法真正考虑它)。 EF 为每个上下文类编译和存储一次模型,因为为每个上下文实例化做所有这些都太昂贵了。

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

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

    【讨论】:

    • 事情更复杂,因为OnModelCreating 默认情况下每个上下文类型(和选项?)只调用一次
    • 我认为应该这样做,至少,我认为这个表名应该只指定一次,即取决于连接的数据库。
    • 我想使用 EF 来处理数据库中的类似表,表名取决于用户输入的某些值,并且是可变的。因此,由于伊万所说的原因,您的方法不起作用。我认为现在这是不可能的,我正在将设计更改为有一个表。无论如何,这可能是一个糟糕的设计。
    • 好的,所以名称必须在一个应用程序的生命周期内多次更改。使用 EF 确实不容易,因为 EF 只初始化(和存储)编译模型一次。您可以为每个表名手动初始化和存储已编译的模型,但如果其他一切都失败并且无法删除要求,那应该是最后的手段。幸运的是,在您的情况下,有更好的选择。
    • @Gert,这可以通过实现自定义IModelCacheKeyFactory 来实现。它是控制模型缓存的服务接口。
    【解决方案2】:

    由于这是一个有趣的问题,可能会帮助需要一些动态模型构建的其他人,这里是如何实现它。

    假设我们有一个自定义上下文,其中包含通过构造函数提供的自定义表名(正如 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 override 里面完成:

    optionsBuilder.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory>();
    

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

    通过在CustomModelCacheKey.key 元组中包含所有自定义状态,可以将相同的技术应用于包含影响状态的自定义模型的任何上下文。当然,它可以在没有值元组的情况下实现,只是使用它们GetHashCode 和Equals 覆盖更容易实现。实际上,自定义服务可以直接返回包含上下文类型和自定义状态成员值的值元组,而不是 CustomModelCacheKey。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-26
      • 1970-01-01
      • 2011-08-28
      • 1970-01-01
      • 2015-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多