Pytanie Ogólna metoda pobierania DbSet z DbContext


Używam Entity Framework z dużą bazą danych (złożoną z ponad 200 tabel).

Próba utworzenia ogólnej metody zwracającej wartość DbSet<T> określonej tabeli T (tj. klasa, która może być TableA).

Klasę encji, która została (automatycznie) utworzona przy użyciu modelu danych encji, wygląda następująco:

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

}

Moja główna klasa jest taka

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
}

Muszę ustawić ograniczenie where T : EntityObject na T być w EntityObject miedza. Jeśli nie było takiego ograniczenia, to DbSet<T> dbSet narzekał (tj. T musi być typem odniesienia), że może dostawać więcej niż oczekuje w kategoriach typów (na podstawie na to).

Problem występuje, gdy próbuję wywołać metodę z określonym typem.

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

Daje błąd:

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

Jak mogę (obsadzić?) TableA wpisz jako EntityObject jeśli wiem, że istnieje dla tego modelu jednostki?

Chociaż jest to niepoprawna składnia (i logika), to właśnie nadąża:

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

Jak mogę zdefiniować TableA (wraz ze wszystkimi pozostałymi 200 tabelami) należy wpisać jako część EntityObject?


Potencjalne rozwiązanie

Okazało się, że moje ograniczenie było zbyt szczegółowe, a wszystko, co musiałem zmienić, pochodziło z where T : IEntity do

where T : class

Więc T jest tym, co DbSet<T> początkowo oczekiwano, typ klasy

Zapisuje problem konieczności dodawania implementacji do ponad 200 klas tabel, TableA, TableB, ...

Oczywiście istnieją inne problemy, takie jak zmiana typu zwrotu z IQueryable<T> do List<T> od czasu IQueryable w przeciwnym razie zostaną zwrócone poza zakresem DbContext (to znaczy. sqlEntities) czyniąc go bezużytecznym.


11
2017-07-08 08:16


pochodzenie


Co chcesz przetestować? - Anatolii Gabuza
Zgaduję, że TableMod.TableA nie dziedziczy po EntityObject? - DavidG
Prawdopodobnie wybierasz wersję EF, która nie wymaga już od niej wszystkich typów jednostek EntityObject. Za pomocą where T : class wystarczy dla twoich celów. Nie ma rzeczywistej potrzeby zmiany mapy dziedziczenia na istniejących typach tylko po to, aby to uwzględnić Nowy metoda, którą piszesz. - Kirill Shlenskiy


Odpowiedzi:


Dlaczego nie spróbujesz zmienić swojego ograniczenia na klasę zamiast EntityObject

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

6
2018-03-11 19:11





Miałem ten sam wymóg i rozwiązałem go, używając:

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

Kod został zaczerpnięty z Używanie Generics do wyszukiwania tabel w Entity Framework  i dostosowane do EF 6 za pomocą DbContext (pierwsza część metody, w której objectcontext jest pobierany z dbcontext

Mam nadzieję, że to pomoże


3
2018-06-19 12:34



Czy jest jakiś powód, dla którego tęsknię, dlaczego wbudowany context.Set<T>() nie zadziała? - Chris Pratt


Nie wiem, jak stworzyłeś swój model, a więc jak wyglądają twoje jednostki. Ale jeśli jest to Code First, klasy encji nie dziedziczą ze wspólnej klasy bazowej, więc nie można dodać ograniczenia typu do generycznego.

Nie polecam używania klasy bazowej, aby móc określić ograniczenie. Znacznie lepiej zrobić to za pomocą interfejsu. Pusty interfejs pozwoli ci określić ograniczenie bez konieczności zmiany klas.

Tak więc, możesz zdefiniować taki interfejs:

public interface IEntity {};

I wtedy:

  • zaimplementuj go we wszystkich klasach, co można zrobić w plikach klas cząstkowych, modyfikując szablon T4 lub w inny sposób w zależności od tego, jak wygląda Twój model
  • użyj go, aby określić ogólny typ ograniczenia za pomocą where IEntity

Jest to najczystszy sposób, aby to zrobić, bez żadnych przeszkód na twoich zajęciach.


0
2017-07-08 08:28



Model OP jest tworzony za pomocą modelu danych encji, co skutkuje plikiem EDMX. To jest pierwsze podejście do bazy danych, więc nie kodujcie najpierw. - jaycer


Kwestia 

Przypuszczam, że twoja TableA klasa nie implementuje EntityObject. Właśnie dlatego otrzymujesz ten błąd. Aby rozwiązać ten problem, możesz mieć abstrakcyjną klasę / interfejs, która będzie podstawą dla wszystkich elementów kontekstu (tj. IContextEntity, która będzie miała unikalną definicję Id):

public class TableA : IContextEntity
{
   ...
}

Ta sama metoda, ale z nowym interfejsem zamiast EntityObject i można to łatwo kpić / testować

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


Użytkowanie (testy)

Drugą ważną rzeczą jest sposób, w jaki chcesz korzystać z tej metody. W przypadku Struktura Entity kontekst to naprawdę ważny mieć separację między integracja i testy jednostkowe. W podanym kodzie próbujesz dotrzeć do bazy danych, co oznacza, że ​​test ten będzie integralny:

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

Łączenie się z bazami danych lub źródłami zewnętrznymi jest zwykle złą praktyką, chyba że wiesz, co robisz i to jest właśnie to. Jeśli potrzebujesz tylko fałszywych / fałszywych danych, aby wykonać na nim akcję - użyj Zatknięcia.


0
2017-07-08 08:26





Dla wszystkich przyszłych pracowników Google'a, mój kolega i ja zhackowaliśmy to w Visual Basic (wersja EF 6). Działa to w naszym przypadku użycia po prostu uzyskanie listy z powrotem, ale prawdopodobnie będzie działać dla innych przypadków użycia. Nie próbuj łapać ani sprawdzać w tym.

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

0
2017-12-01 12:09



Nawiasem mówiąc, można to zrobić do pracy z plikiem DBML, gdy używasz Linq do Sql. Po prostu zmieniasz ctx na kontekst DBML, a nie na kontekst EF i musisz zastąpić Set z GetTable. - Richard Griffiths