2014-07-08 15 views
11

Sto utilizzando Entity Framework con un database di grandi dimensioni (composto da più di 200 tabelle).Metodo generico per recuperare DbSet <T> da DbContext

Cercando di creare un metodo generico che restituisce il DbSet<T> di un'apposita tabella T (cioè classe, che può essere TableA).

La classe entità che era (automaticamente) creati utilizzando il modello di dati dell'entità assomiglia così:

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 

} 

La mia classe principale è come questo

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 
} 

devo impostare un vincolo where T : EntityObject su T per rientrare nei limiti EntityObject. Se non ci fosse un tale vincolo allora il DbSet<T> dbSet si lamenterebbe (ad esempio T deve essere un tipo di riferimento) che potrebbe ottenere più del previsto in termini di tipi (based su questo).

Il problema si verifica quando provo a chiamare effettivamente il metodo con un tipo specifico.

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

dà un errore:

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

Come posso (cast?) Il tipo TableA come EntityObject se so che esiste per quel modello entità?

anche se questo è sintassi non corretta (e la logica) questo è quello che sto cercando:

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

Come posso definire il TableA (insieme a tutti gli altri 200 tabelle) tipo ad essere una parte di EntityObject?


Una possibile soluzione

Attiva il mio vincolo era troppo specifico, tutto quello che dovevo cambiare stato da where T : IEntity a

where T : class 

Così il T è ciò che il DbSet<T> inizialmente previsto, un tipo di classe

Salva il problema di havin g aggiungere implementazioni alle classi tabella 200+, TableA, TableB ...

Poi naturalmente ci sono altri problemi come la modifica del tipo di ritorno da IQueryable<T> al List<T> poiché il IQueryable sarebbero altrimenti restituiti al di fuori del campo di applicazione DbContext (es sqlEntities) rendendolo inutile.

+0

Che cosa vuoi testare? –

+0

Supponendo che 'TableMod.TableA' non erediti da' EntityObject'? – DavidG

+2

Probabilmente stai prendendo di mira una versione di EF che non richiede più che tutti i tipi di entità derivino da "EntityObject". Usando 'dove T: class' sarà sufficiente per i tuoi scopi. Non è necessario modificare la mappa di ereditarietà sui tipi esistenti solo per adattarsi a questo * nuovo * metodo che si sta scrivendo. –

risposta

0

Problema

suppongo tua classe TableA non implementa EntityObject.Ecco perché stai ricevendo questo errore. Per risolvere questo si può avere una classe/interfaccia astratta che sarà base per tutte le entità di contesto (cioè IContextEntity che avrà definizione ID univoco):

public class TableA : IContextEntity 
{ 
    ... 
} 

Poi lo stesso metodo, ma con la nuova interfaccia, invece di EntityObject e si può finto/test facilmente

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


Usage (Test)

seconda cosa importante è il modo in cui si desidera utilizzare t il suo metodo In caso di Entity Framework contesto è molto importante di avere separazione tra integrazione e unità test. Nel codice che, se stai cercando di raggiungere banca dati che significa che questo test sarà l'integrazione:

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

Collegamento a un database o fonti esterne è di solito una cattiva pratica se non si sa che cosa fare, e questo è esattamente lo . Se hai solo bisogno di dati falsi/fittizi per eseguire un'azione su di esso - usa Stub.

0

Non so come è stato creato il modello, e quindi come appaiono le entità. Ma, se è il codice prima, le classi di entità non ereditano da una classe base comune, quindi non è possibile aggiungere un vincolo di tipo al proprio generico.

Non è consigliabile utilizzare una classe base per poter specificare un vincolo. È molto meglio farlo usando un'interfaccia. Un'interfaccia vuota ti consentirà di specificare un vincolo senza dover modificare le tue classi.

Allora, che cosa si può fare è di definire un'interfaccia simile a questo:

public interface IEntity {}; 

E poi:

  • attuare in tutte le classi, che può essere fatto in file classi parziali, la modifica di un Modello T4 o in qualche altro modo a seconda di come appare il tuo modello
  • usalo per specificare il tipo generico vincolo con where IEntity

Questo è il modo più semplice per farlo, senza interferenze con le lezioni.

+0

Il modello dell'OP viene creato tramite il modello dati dell'entità, che risulta in un file EDMX. Questo è un primo approccio al database, quindi non il codice prima. – jaycer

3

Ho avuto lo stesso requisito e risolto utilizzando la seguente:

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

Il codice è stato preso da Using Generics for Lookup Tables in Entity Framework e adattato per EF 6 usando il DbContext (prima parte del metodo in cui il objectcontext viene estratto dal il dbcontext

Speranza che aiuta

+2

C'è qualche motivo per cui mi manca perché il 'context.Set ()' incorporato non funziona? –

5

Perché non provare a cambiare il vostro vincolo di classe invece di EntityObject

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

Per i futuri googler, il mio collega e io l'abbiamo appena violato in Visual Basic (versione EF 6). Funziona per il nostro caso di semplice recupero di una lista, ma probabilmente funzionerà anche per gli altri casi d'uso. Nessun tentativo di cattura o di controllo in questo.

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

Per inciso, questo può essere fatto per funzionare con un file DBML quando si usa Linq su Sql. È sufficiente modificare il ctx per essere il contesto DBML non il contesto EF e è necessario sostituire Set con GetTable. –

Problemi correlati