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

时间:2012-09-29 03:57:35

标签: asp.net-mvc-3 entity-framework-4 ef-code-first dbset

我是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 个答案:

答案 0 :(得分:4)

我怀疑EF只反映了派生DbSet<T>中的通用DbContext属性,并在内存中创建模型时忽略了任何非泛型DbSet属性。

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

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

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

关于使用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<T>方法。

OnModelCreating中的上述代码是......

的动态对应代码
modelBuilder.Entity<LogRec>();

...将与静态LogRec类型一起使用,并且只将该类型作为EF已知的实体。这与向上下文类添加DbSet<LogRec>属性完全相同。

您应该能够使用...

访问动态实体的实体集
context.Set(LogRecType)

...将返回非通用DbSet

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