C#,MVC3,如何将非泛型 DBSet 与运行时定义的类型一起使用?

Posted

技术标签:

【中文标题】C#,MVC3,如何将非泛型 DBSet 与运行时定义的类型一起使用?【英文标题】:C#, MVC3, How to use the non-generic DBSet with a runtime defined type? 【发布时间】:2012-09-29 03:57:34 【问题描述】:

我是 MVC 和 EF 的新手。我的应用是一个简单的代码优先,有几个 POCO 类和一个 DBContext,如下所示:

   public class ExpDefContext : DbContext
   
      public DbSet<Experiment> Experiments  get; set; 
      public DbSet<Research> Researches  get; set; 
      ...

问题:我需要在我的数据模型中添加一个实体集,它的类型是在运行时根据用户输入构建的,这意味着我不知道它的数据结构。

我读到非泛型 Dbset 类就是为此而制作的,所以我添加到上下文中:

    public DbSet Log  get; set; 

...并为接受运行时类型并设置新 Dbset 的上下文创建了一个构造函数:

        public ExpDefContext(Type LogRecType)
        
            Log = Set(LogRecType);
        

(顺便说一下,类型是使用 Reflection.Emit 构建的)。

在控制器中,我创建类型(名为 LogRec)并将其传递给新的 DBContext 实例。然后我创建一个 LogRec 实例并尝试将其添加到数据库中:

    Type LogRec;
    LogRec = LogTypeBuilder.Build(dbExpDef, _experimentID);
    var dbLog = new ExpDefContext(LogRec);
    var testRec = LogRec.GetConstructor(Type.EmptyTypes).Invoke(Type.EmptyTypes);
    dbLog.Log.Add(testRec);
    dbLog.SaveChanges();

我从 dbLog.Log.Add(testRec) 得到一个异常:

实体类型 LogRec 不是当前上下文模型的一部分

我做错了什么? 有没有更好的方法来做到这一点(最好不要深入实体框架)?

谢谢

【问题讨论】:

【参考方案1】:

我怀疑 EF 仅反映派生 DbContext 中的通用 DbSet&lt;T&gt; 属性,并在内存中创建模型时忽略任何非通用 DbSet 属性。

但是,另一种方法可能是使用 OnModelCreating 中的 Fluent API 将您的动态类型作为实体添加到模型中。

首先,只有在第一次加载 AppDomain 时在内存中构建模型时,才能向模型添加类型。 (每个 AppDomain 只构建一个模型。)如果除了重载的构造函数之外,您还有一个上下文的默认构造函数,并且使用此默认构造函数创建和使用了上下文实例,那么您的模型将仅使用静态类型构建,并且只要 AppDomain 存在,您就不能再将动态类型用作实体。这将导致您遇到的异常。

要考虑的另一点是数据库模式的创建。如果您的类型在编译时未知,则数据库架构在编译时是未知的。如果模型在下次运行应用程序时由于新类型而发生更改,您将需要以某种方式更新数据库模式,方法是从头开始重新创建数据库,或者定义一个自定义数据库初始化程序,该初始化程序仅删除 LogRec 表并创建根据LogRec 类型的新布局创建一个新表。或者,代码优先迁移可能会有所帮助。

关于 Fluent API 的可能解决方案:

删除DbSet 并将Type 成员添加到上下文并覆盖OnModelCreating

public class ExpDefContext : DbContext

    private readonly Type _logRecType;

    public ExpDefContext(Type LogRecType)
    
        _logRecType = LogRecType;
    

    public DbSet<Experiment> Experiments  get; set; 
    public DbSet<Research> Researches  get; set; 

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
        entityMethod.MakeGenericMethod(_logRecType)
            .Invoke(modelBuilder, new object[]  );
            

DbModelBuilder 没有非泛型 Entity 方法,因此需要动态调用泛型 Entity&lt;T&gt; 方法。

OnModelCreating 中的上述代码是...的动态对应物

modelBuilder.Entity<LogRec>();

...它将与静态 LogRec 类型一起使用,并且只会使该类型成为 EF 已知的实体。与在上下文类中添加DbSet&lt;LogRec&gt; 属性完全相同。

您应该能够通过使用...访问动态实体的实体集。

context.Set(LogRecType)

...这将返回一个非通用的DbSet

我不知道这是否可行并且没有测试它但the idea is from Rowan Miller, member of the EF team,所以我有一些希望它会。

【讨论】:

非常感谢。我以前看过 Rowan Miller 的建议,但可以这么说,你把它放在正确的上下文中。关于我的代码具有默认构造函数并且模型是在调用参数化模型之前构建的,您也是正确的。我不知道如何克服这个问题,因为我的动态类型依赖于 Experiments Dbset 中的一个值。它就像分步构建模型:首先是 Experiments,用户选择一个值来决定类型,然后是非泛型 LogRec。有什么建议和线索吗? @user1707621:我有两个想法你可以试试:1)context.Database.Initialize(true);:参数true是“强制”初始化。我不确定这是否会在内存中重新创建模型或仅再次运行 DB 初始化程序(查看 intellisense 中的描述)。 2) 您可以尝试使用两种不同的上下文类,一种没有LogRecType,一种有它。两个上下文类应该在内存中产生两个模型。这都是相当困难和高级的东西。如果遇到困难,我建议您创建新问题。

以上是关于C#,MVC3,如何将非泛型 DBSet 与运行时定义的类型一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的泛型与非泛型性能

为啥 C# 无法从非泛型静态方法的签名推断泛型类型参数类型?

C#集合

C# 2.0 新特性(上)

泛型集合与非泛型集合的异同

C#泛型Dictionary的用法实例详解