【问题标题】:Generic method to retrieve DbSet<T> from DbContext从 DbContext 检索 DbSet<T> 的通用方法
【发布时间】:2014-07-08 08:16:38
【问题描述】:

我正在使用带有大型数据库(由 200 多个表组成)的实体框架。

尝试创建一个泛型方法,该方法返回特定表T(即类,可以是TableA)的DbSet&lt;T&gt;

使用实体数据模型(自动)创建的实体类如下所示:

public partial class sqlEntities : DbContext
{

    public virtual DbSet<TableA> TableA { get; set; }
    public virtual DbSet<TableB> TableB { get; set; }
    public virtual DbSet<TableC> TableC { get; set; }
    ... // other methods

}

我的主班是这样的

public class TableModifier
{
   // Should return first 10 elements from a table of that type T
   public IQueryable<T> GetFromDatabase<T>() where T : EntityObject
   {
       try
       {
           using (sqlEntities ctx = new sqlEntities())
           {
               // Get the DbSet of the type T from the entities model (i.e. DB)
               DbSet<T> dbSet = ctx.Set<T>();
               return dbSet.Take(10);
           }
       }
       catch (Exception ex)
       {
           // Invalid type was provided (i.e. table does not exist in database)
           throw new ArgumentException("Invalid Entity", ex);
       }
   }
   ... // other methods
}

我必须在T 上设置一个约束where T : EntityObject,使其在EntityObject 范围内。如果没有这样的约束,那么DbSet&lt;T&gt; dbSet 会抱怨(即T 必须是引用类型)它可能会在类型方面得到比预期更多的东西(based 对此)。

当我尝试实际调用具有特定类型的方法时会出现问题。

 [TestMethod]
 public void Test_GetTest()
 {
     TableModifier t_modifier = new TableModifier();

     // The get method now only accepts types of type EntityObject
     IQueryable<TableA> i_q = t_modifier.GetFromDatabase<TableA>();
 }

它给出了一个错误:

There is no implicit reference conversion from 'TableMod.TableA' to
'System.Data.Entity.Core.Objects.DataClasses.EntityObject'.

如果我知道该实体模型存在 TableA 类型,我如何(转换?)为 EntityObject

虽然这是不正确的语法(和逻辑),但这是我所追求的:

 t_modifier.GetFromDatabase<(EntityObject)TableA>();

如何将TableA(以及所有其他200 个表)类型定义为EntityObject 的一部分?


一个潜在的解决方案

原来我的约束太具体了,我需要改变的只是从where T : IEntity

where T : class

所以TDbSet&lt;T&gt; 最初期望的,一个类类型

省去了向 200 多个表类添加实现的麻烦,TableATableB、...

当然还有其他问题,例如将返回类型从IQueryable&lt;T&gt; 更改为List&lt;T&gt;,因为IQueryable 将在DbContext 的范围之外返回(即sqlEntities)使其无用。

【问题讨论】:

  • 你想测试什么?
  • 猜测TableMod.TableA 不继承自EntityObject
  • 您可能针对的 EF 版本不再要求所有实体类型都派生自 EntityObject。使用 where T : class 足以满足您的目的。没有必要仅仅为了适应您正在编写的这个 new 方法而更改现有类型的继承映射。

标签: c# entity-framework generics


【解决方案1】:

为什么不尝试将约束更改为 class 而不是 EntityObject

public IQueryable<T> GetFromDatabase<T>() where T : class

【讨论】:

    【解决方案2】:

    我有同样的要求并通过使用以下方法解决了它:

    public static void GetEntitiesGeneric<TEntity>()// where TEntity : System.Data.Entity.Core.Objects.DataClasses.EntityObject  <-- NO LONGER NEEDED
    {
        try
        {
            var key = typeof(TEntity).Name;
            var adapter = (IObjectContextAdapter)MyDbContext;
            var objectContext = adapter.ObjectContext;
            // 1. we need the container for the conceptual model
            var container = objectContext.MetadataWorkspace.GetEntityContainer(
                objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
            // 2. we need the name given to the element set in that conceptual model
            var name = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault().Name;
            // 3. finally, we can create a basic query for this set
            var query = objectContext.CreateQuery<TEntity>("[" + name + "]");
    
            // Work with your query ...
        }
        catch (Exception ex)
        {
            throw new ArgumentException("Invalid Entity Type supplied for Lookup", ex);
        }
    }
    

    代码取自 Using Generics for Lookup Tables in Entity Framework ,并使用 DbContext 适应 EF 6(方法的第一部分,其中 objectcontext 是从 dbcontext 中提取的

    希望对你有帮助

    【讨论】:

    • 有什么原因我错过了为什么内置的context.Set&lt;T&gt;() 不起作用?
    【解决方案3】:

    我不知道您是如何创建模型的,因此不知道您的实体是什么样的。但是,如果它是 Code First,实体类不会继承自公共基类,因此您不能向泛型添加类型约束。

    我不建议使用基类来指定约束。使用界面来做这件事要好得多。一个空的接口将允许您指定一个约束,而无需更改您的类。

    所以,你可以做的就是定义一个这样的接口:

    public interface IEntity {};
    

    然后:

    • 在所有类中实现它,这可以在部分类文件中完成,修改 T4 模板或以其他方式,具体取决于您的模型的外观
    • 用它来指定泛型类型约束where IEntity

    这是最干净的方法,不会对您的课程造成任何干扰。

    【讨论】:

    • OP 的模型是通过实体数据模型创建的,这会产生一个 EDMX 文件。这是数据库优先的方法,所以不是代码优先。
    【解决方案4】:

    问题

    我想你的TableA 类没有实现EntityObject。这就是您收到此错误的原因。为了解决这个问题,您可以拥有一个抽象类/接口,它将作为所有上下文实体的基础(即具有唯一 Id 定义的 IContextEntity):

    public class TableA : IContextEntity
    {
       ...
    }
    

    然后使用相同的方法,但使用新界面而不是EntityObject,您可以轻松地模拟/测试它

    public IQueryable<T> GetFromDatabase<T>() where T : IContextEntity
    {
         ...
    }
    


    使用(测试)

    第二个重要的事情是你想使用这个方法的方式。在 Entity Framework 上下文的情况下,将 集成单元测试 分开是真的重要。在您提供的代码中,您正在尝试访问数据库,这意味着该测试将是集成:

    using (sqlEntities ctx = new sqlEntities()) // This will open a DB connection
    

    连接到数据库或外部资源通常是一种不好的做法,除非您知道自己在做什么,而事实正是如此。如果您只需要一些虚假/虚拟数据来对其执行操作 - 使用 Stubs

    【讨论】:

      【解决方案5】:

      对于任何未来的谷歌员工,我和我的同事刚刚在 Visual Basic(EF 6 版本)中解决了这个问题。它适用于我们简单地获取列表的用例,但可能适用于其他用例。没有尝试捕获或签入这个。

      Private Class getList(Of T As Class)
          Public Shared Function getList() As List(Of T)
              Using ctx As New MVPBTEntities()
                  ' Get the DbSet of the type T from the entities model (i.e. DB)
                  Dim dbSet = ctx.Set(Of T)()
                  Return dbSet.ToList
              End Using
          End Function
      End Class
      

      【讨论】:

      • 顺便说一下,当您使用 Linq to Sql 时,这可以与 DBML 文件一起使用。您只需将 ctx 更改为 DBML 上下文而不是 EF 上下文,您需要将 Set 替换为 GetTable。
      猜你喜欢
      • 1970-01-01
      • 2012-11-11
      • 1970-01-01
      • 2016-02-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多