2013-08-05 16 views
5

Considerare un database con più tabelle create utilizzando prima il codice Entity Framework. Ogni tabella contiene un diverso tipo di oggetto, ma desidero creare una singola classe generica di query query per motivi di estensibilità. Per quanto riguarda un quadro di riferimento per questa classe ho una classe generica come così destinato a fungere da un wrapper per LINQ to SQL:Come creare un quesito generico di Linq C#?

public class DBQuerier<T> 
    where T : class 
{ 
    DbSet<T> relation; 

    public DBQuerier(DbSet<T> table) 
    { 
     relation = table; 
    } 

    public bool Exists(T toCheck); 
    public void Add(T toAdd); 
    public T (Get Dictionary<String, Object> fields); 
    public bool SubmitChanges(); 
    public void Update(T toUpdate, Dictionary<String, Object> fields); 
    public void Delete(T toDelete); 
} 

Il mio problema arriva al primo ostacolo quando si cerca di verificare se esiste un record come non riesco a convertire tra il tipo generico T e un tipo di oggetto con cui sto provando a lavorare. Se uso di base Linq:

public bool Exists(T toCheck) 
{ 
    return (from row in relation 
     where row.Equals(toCheck) 
     select row).Any(); 
} 

un'eccezione runtime si verifica come SQL non può funzionare con qualsiasi cosa, ma i tipi primitivi, anche se a implementare IComparable e designare i miei Equals che confronta un singolo campo. Espressioni lambda sembrano avvicinarsi, ma poi ho ottenere problemi di nuovo con SQL non essere in grado di gestire più di tipi primitivi anche se la mia comprensione è che Expression.Equal costretta a utilizzare la classe funzione di paragonabile:

public bool Exists(T toCheck) 
{ 
    ParameterExpression T1 = Expression.Parameter(typeof(myType), "T1"); 
    ParameterExpression T2 = Expression.Parameter(typeof(myType), "T2"); 
    BinaryExpression compare = Expression.Equal(T1, T2); 
    Func<T, T, bool> checker = 
     Expression.Lambda<Func<T, T, bool>> 
      (compare, new ParameterExpression[] { T1, T2 }).Compile(); 
    return relation.Where(r => checker.Invoke(r, toCheck)).Any(); 
} 

L'espressione l'albero è stato progettato in mente in modo che in seguito potessi aggiungere un'istruzione switch per costruire la query in base al tipo che stavo cercando di guardare. La mia domanda è: c'è un modo molto più semplice/migliore per farlo (o correggere quello che ho provato finora) visto che le uniche altre opzioni che posso vedere sono scrivere una classe per ogni tabella (non così facile da estendere) o controlla ogni lato di applicazione del record (potenzialmente terribilmente lento se devi trasferire l'intero database!)? Mi scuso se ho commesso errori così basilari in quanto non ho lavorato molto per molto tempo, grazie in anticipo!

+0

Nota il metodo 'Exists' è solo un rinominato metodo' Contains' senza un'implementazione valida. – Servy

+0

Se si dispone di una colonna della chiave primaria, è sempre possibile utilizzare il metodo 'relation.Find (Id)' per verificare se esiste. – Nilesh

risposta

2

È improbabile che il framework Entity funzioni con il proprio linq personalizzato, è piuttosto rigido nei comandi che supporta. Ho intenzione di divagare un po 'ed è pseudocodice, ma ho trovato due soluzioni che hanno funzionato per me.

Ho usato per la prima volta l'approccio generico, in cui il mio cercatore di database generico avrebbe accettato un Func<T, string> nameProperty per accedere al nome che stavo per interrogare. EF ha molti sovraccarichi per l'accesso a set e proprietà, quindi potrei farlo funzionare, passando in c => c.CatName e usando quello per accedere alla proprietà in modo generico. Tuttavia, è stato un po 'complicato:

Successivamente ho effettuato il refactoring per utilizzare le interfacce.

Ho una funzione che esegue una ricerca di testo su qualsiasi tabella/colonna passate nel metodo.

Ho creato un'interfaccia denominata INameSearchable che contiene semplicemente una proprietà che sarà la proprietà nome da cercare. Ho quindi esteso i miei oggetti entità (sono classi parziali) per implementare INameSearchable. Quindi ho un'entità chiamata Cat che ha una proprietà CatName. Ho usato l'interfaccia per return CatName; come proprietà Name dell'interfaccia.

Posso quindi creare un metodo di ricerca generico where T : INameSearchable ed esporre la proprietà Name che la mia interfaccia ha esposto. Quindi, semplicemente, lo uso nel mio metodo per eseguire la query, ad es. (Pseudocodice a memoria!)

doSearch(myContext.Cats);

e nel metodo

public IEnumerable<T> DoSearch<T>(IDbSet<T> mySet, string catName) 
{ 
    return mySet.Where(c => c.Name == catName); 
} 

E molto bello, mi permette di cercare genericamente nulla.

Spero che questo aiuti.

+0

Sono interessato a te soluzione. Potresti essere abbastanza gentile da elaborare sulla tua interfaccia: INameSearchable e come lo hai implementato? Suppongo che stia estendendo la classe di contesto EF? che verrebbe sovrascritto ogni volta che aggiorni il modello? – DaniDev

+0

Bella soluzione però! Sarebbe stato più comprensibile con un po 'di codice in più per renderlo più espressivo. 1 però :) – Irfan

6

Non compilarlo. Func<T,bool> significa "esegui questo in memoria" mentre Expression<Func<T,bool>> significa "mantieni l'idea logica di ciò che questo predicato è" che consente a framework come Entity Framework di tradurlo nella query.

Come nota a margine, non penso che Entity Framework consente di fare a.Equals(b) per l'interrogazione, quindi dovrete fare a.Id == b.Id

0

Se si desidera utilizzare EntityFramework è necessario utilizzare tipi primitivi. Il motivo è che la tua espressione LINQ viene convertita in un'istruzione SQL. SQL non sa nulla di oggetti, IComparables, ...

Se non è necessario che sia in SQL, è necessario prima eseguire la query su SQL e quindi filtrarla in memoria. È possibile farlo con i metodi attualmente in uso

Problemi correlati