2

我是 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 不是当前上下文模型的一部分

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

谢谢

4

1 回答 1

4

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

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

首先,只有在第一次加载 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<T>方法。

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

modelBuilder.Entity<LogRec>();

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

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

context.Set(LogRecType)

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

我不知道这是否可行并且没有对其进行测试,但这个想法来自 EF 团队成员 Rowan Miller,所以我有一些希望。

于 2012-09-29T14:34:17.397 回答