Общий метод для извлечения DbSet <T> из DbContext

Я использую Entity Framework с большой базой данных (состоящей из более чем 200 таблиц).

Попытка создать общий метод, который возвращает DbSet<T> конкретной таблицы T (т.е. класс, который может быть TableA).

Класс сущности, который был (автоматически) создан с использованием модели данных сущности, выглядит так:

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
}

Мне нужно установить ограничение 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'.

Как я могу (cast?) тип TableA как EntityObject, если я знаю, что он существует для этой модели сущности?

Хотя это неправильный синтаксис (и логика), это то, что мне нужно:

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

Как я могу определить TableA (вместе со всеми другими 200 таблицами), чтобы быть частью EntityObject?


Потенциальное решение

Оказывается, мое ограничение было слишком специфичным, все, что мне нужно было изменить, было от where T : IEntity до

where T : class

Итак, T - это то, что первоначально ожидалось DbSet<T>, тип класса

Сохраняет проблему добавления реализаций к 200 + классам таблиц, TableA, TableB,...

Тогда, конечно, существуют и другие проблемы, такие как изменение типа возврата из IQueryable<T> в List<T>, поскольку в противном случае IQueryable был бы возвращен за пределы области DbContext (т.е. sqlEntities), что делает его бесполезным.

Ответ 1

Почему бы вам не попробовать изменить ограничение на класс вместо 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);
    }
}

Код был взят из Использование обобщений для таблиц поиска в Entity Framework и адаптирован для EF 6 с использованием DbContext (первая часть метода, где objectcontext извлекается из dbcontext

Надеюсь, это поможет

Ответ 3

Проблема

Я полагаю, что ваш класс 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

Подключение к базам данных или внешним источникам, как правило, является плохой практикой, если вы не знаете, что вы делаете, и это именно так. Если вам просто нужны фальшивые/фиктивные данные для выполнения действия над ним, используйте Stubs.

Ответ 4

Я не знаю, как вы создали свою модель и как выглядят ваши сущности. Но, если это Code First, классы сущностей не наследуются от общего базового класса, поэтому вы не можете добавить ограничение типа к своему родовому.

Я не рекомендую использовать базовый класс, чтобы иметь возможность указать ограничение. Это намного лучше сделать с помощью интерфейса. Пустой интерфейс позволит вам указать ограничение без необходимости менять классы вообще.

Итак, что вы можете сделать, это определить такой интерфейс:

public interface IEntity {};

И затем:

  • реализовать его во всех классах, что может быть сделано в файлах частичных классов, изменении шаблона T4 или каким-либо другим способом в зависимости от того, как выглядит ваша модель.
  • используйте его для указания ограничения типа generic с помощью where IEntity

Это самый чистый способ сделать это без каких-либо помех вашим классам.

Ответ 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