从DbContext中检索DbSet<T>的通用方法

22

我正在使用Entity Framework处理一个大型数据库(由200多个表组成)。

试图创建一个通用方法,返回特定表T(即类,可以是TableA)的DbSet<T>

使用实体数据模型自动生成的实体类如下:

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,以确保T处于EntityObject的范围内。如果没有这样的约束,那么DbSet<T> dbSet将会出现问题(即T必须是引用类型),因为它可能会得到更多意外的类型(基于此文)。当我尝试使用特定类型调用该方法时,就会出现问题。
 [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的一部分?


一个可能的解决方案

结果证明我的限制条件过于具体,我只需要将T:IEntity改为

where T : class

因此,TDbSet<T>最初期望的类类型。

省去了将实现添加到200多个表类(TableATableB等)的麻烦。

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


你想要测试什么? - Anatolii Gabuza
猜测 TableMod.TableA 没有继承自 EntityObject - DavidG
2
你可能正在针对一个不再需要所有实体类型派生自“EntityObject”的EF版本进行开发。使用“where T:class”就足以满足你的需求了。没有必要改变现有类型的继承映射,只是为了适应你正在编写的这个方法。 - Kirill Shlenskiy
5个回答

15
为什么不尝试将您的约束更改为类,而不是 EntityObject?
public IQueryable<T> GetFromDatabase<T>() where T : class

4

我有同样的需求,并通过以下方式解决:

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);
    }
}

该代码取自在Entity Framework中使用通用表进行查找,并适配于EF 6,使用DbContext(方法的第一部分提取从dbcontext中提取)

希望对你有所帮助


5
为什么我不能使用内置的 context.Set<T>() 方法?有什么原因我不知道吗? - Chris Pratt

0

问题

我猜测你的TableA类没有实现EntityObject接口。这就是为什么你会得到这个错误。要解决这个问题,你可以创建一个抽象类/接口,作为所有上下文实体的基类(例如,IContextEntity,其中将定义唯一的Id):

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

连接到数据库或外部数据源通常是不好的实践,除非你知道你在做什么,这正是如此。如果你只需要一些虚假/虚拟数据来执行操作,请使用存根


0

我不知道你是如何创建你的模型,以及你的实体类是什么样子的。但是,如果是Code First,实体类不会继承自一个公共基类,因此你无法为你的泛型添加类型约束。

我不建议使用一个基类来指定约束条件。最好使用接口来实现。一个空接口将允许你指定约束条件,而无需改变你的类。

所以,你可以定义一个像这样的接口:

public interface IEntity {};

然后:

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

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


OP的模型是通过实体数据模型创建的,这会生成一个EDMX文件。这是一种数据库优先的方法,而不是代码优先。 - jaycer

0

对于未来的googler们,我和我的同事刚刚用Visual Basic(EF 6版本)完成了这个小程序。它适用于我们简单地获取列表的使用情况,对于其他用例可能也适用。这个程序没有try catch或者检查。

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即可。 - Richard Griffiths

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接