I repository dovrebbero restituire IQueryable?


66

Ho visto molti progetti con repository che restituiscono istanze di IQueryable. Ciò consente ulteriori filtri e l'ordinamento può essere eseguito su un IQueryablealtro codice, che si traduce in diversi SQL generati. Sono curioso di sapere da dove provenga questo schema e se sia una buona idea.

La mia più grande preoccupazione è che IQueryableè una promessa di colpire il database qualche tempo dopo, quando viene elencato. Ciò significa che un errore verrebbe generato all'esterno del repository. Ciò potrebbe significare che un'eccezione Entity Framework viene generata in un diverso livello dell'applicazione.

In passato ho anche riscontrato problemi con i set di risultati attivi multipli (MARS) (soprattutto quando si utilizzano le transazioni) e questo approccio sembra che ciò comporterebbe che ciò accada più spesso.

Ho sempre chiamato AsEnumerableo ToArrayalla fine di ciascuna delle mie espressioni LINQ per assicurarmi che il database venisse colpito prima di lasciare il codice del repository.

Mi chiedo se la restituzione IQueryablepotrebbe essere utile come blocco predefinito per un livello dati. Ho visto del codice piuttosto stravagante con un repository che chiama un altro repository per crearne uno ancora più grande IQueryable.


Quale sarebbe la differenza se il db non fosse disponibile al momento dell'esecuzione della query differita rispetto al momento della costruzione della query? Il codice di consumo dovrebbe affrontare questa situazione a prescindere.
Steven Evers,

Domanda interessante, ma probabilmente sarà difficile rispondere. Una semplice ricerca mostra un acceso dibattito sulla tua domanda: duckduckgo.com/?q=repository+return+iqueryable
Corbin,

6
Tutto dipende dal fatto che tu voglia consentire ai tuoi clienti di aggiungere clausole Linq che potrebbero beneficiare di un'esecuzione differita. Se aggiungono una whereclausola a un differito IQueryable, devi solo inviare quei dati via cavo, non l'intero set di risultati.
Robert Harvey,

Se si utilizza il modello di repository - no, non è necessario restituire IQueryable. Ecco un bel perché .
Arnis Lapsa,

1
@SteveEvers C'è un'enorme differenza. Se viene generata un'eccezione nel mio repository, posso racchiuderla in un tipo di eccezione specifico del livello dati. Se succede in seguito, devo catturare il tipo di eccezione originale lì. Più sono vicino alla fonte dell'eccezione, più è probabile che sappia cosa l'ha causata. Questo è importante per la creazione di messaggi di errore significativi. IMHO, consentire a un'eccezione specifica di EF di lasciare il mio repository costituisce una violazione dell'incapsulamento.
Travis Parks,

Risposte:


47

La restituzione di IQueryable offrirà sicuramente maggiore flessibilità ai consumatori del repository. Mette la responsabilità di restringere i risultati al cliente, che naturalmente può essere sia un vantaggio che una stampella.

Sul lato positivo, non sarà necessario creare tonnellate di metodi di repository (almeno su questo livello) - GetAllActiveItems, GetAllNonActiveItems, ecc. - per ottenere i dati desiderati. A seconda delle tue preferenze, di nuovo, questo potrebbe essere buono o cattivo. Si sarà (/ dovrebbe) necessario definire contratti comportamentali che le vostre implementazioni aderiscono, ma dove che va dipende da voi.

Quindi potresti mettere la logica di recupero grintosa al di fuori del repository e lasciarla usare come desidera l'utente. Quindi esporre IQueryable offre la massima flessibilità e consente query efficienti rispetto al filtraggio in memoria, ecc. E potrebbe ridurre la necessità di creare una tonnellata di metodi specifici per il recupero dei dati.

D'altra parte, ora hai dato ai tuoi utenti un fucile da caccia. Possono fare cose che potresti non aver inteso (abusando di .include (), fare query pesanti e pesanti e filtrare in memoria nelle loro rispettive implementazioni, ecc.), Che sostanzialmente farebbero da contropiede ai controlli di stratificazione e comportamentali perché hai dato accesso completo.

Quindi, a seconda del team, della loro esperienza, delle dimensioni dell'app, della stratificazione generale e dell'architettura ... dipende: - \


Si noti che è possibile, se si desidera lavorare per esso, restituire IQueryable, che a sua volta chiama il DB, ma aggiunge i livelli e i controlli comportamentali.
psr il

1
@psr Potresti spiegare cosa intendi con questo?
Travis Parks,

1
@TravisParks - IQueryable non deve essere implementato dal DB, puoi scrivere tu stesso le classi IQueryable - è solo piuttosto difficile. Quindi potresti scrivere un wrapper IQueryable attorno a un database IQueryable e avere il tuo limite IQueryable pieno accesso al database sottostante. Ma questo è abbastanza difficile che non mi aspetterei che venga fatto ampiamente.
sal

2
Si noti che anche quando non si restituisce IQueryables, è comunque possibile evitare di dover creare molti metodi (GetAllActiveItems, GetAllNonActiveItems, ecc.) Semplicemente passando un Expression<Func<Foo,bool>>metodo al proprio metodo. Questi parametri possono essere passati Wheredirettamente al metodo, consentendo così al consumatore di scegliere il proprio filtro senza la necessità di accedere direttamente a IQueryable <Foo>.
Flater,

1
@hanzolo: dipende da cosa intendi con refactoring. È vero che cambiare il design (ad esempio aggiungendo nuovi campi o cambiando le relazioni) è più doloroso quando si dispone di una base di codice stratificata e liberamente accoppiata; ma il refactoring è meno doloroso quando devi cambiare solo uno strato. Tutto dipende dal fatto che ti aspetti di ridisegnare l'applicazione o se stai semplicemente riformattando l'implementazione della stessa architettura di base. Vorrei comunque sostenere l'accoppiamento libero in entrambi i casi, ma non sbagli che si aggiunge al refactoring quando si cambiano i concetti di dominio di base.
Flater,

29

Esporre IQueryable a interfacce pubbliche non è una buona pratica. Il motivo è fondamentale: non è possibile fornire la realizzazione di IQueryable come si dice.

L'interfaccia pubblica è un contratto tra provider e clienti. E nella maggior parte dei casi c'è un'aspettativa di piena attuazione. IQueryable è un cheat:

  1. IQueryable è quasi impossibile da implementare. E attualmente non esiste una soluzione adeguata. Anche Entity Framework ha molte UnsupportedExceptions .
  2. Oggi i provider di query hanno una grande dipendenza dall'infrastruttura. Sharepoint ha un'implementazione parziale propria e il provider My SQL ha un'implementazione parziale distinta.

Il modello del repository ci dà una chiara rappresentazione mediante stratificazione e, soprattutto, indipendenza di realizzazione. Quindi possiamo cambiare in futuro fonte di dati.

La domanda è " Dovrebbe esserci la possibilità di sostituire l'origine dati? ".

Se domani parte dei dati può essere spostata nei servizi SAP, nella banca dati NoSQL o semplicemente in file di testo, possiamo garantire la corretta implementazione dell'interfaccia IQueryable?

( altri punti positivi sul perché questa è una cattiva pratica)


Questa risposta non è autonoma senza consultare il link esterno volatile. Considera di riassumere almeno i punti chiave sollevati dalla risorsa esterna e cerca di mantenere l'opinione personale fuori dalla tua risposta ("peggior idea che abbia sentito"). Considera anche di rimuovere lo snippet di codice che non sembra correlato a nulla di IQueryable.
Lars Viklund,

1
Grazie @LarsViklund, la risposta viene aggiornata con esempi e descrizione del problema
Artru

19

Realisticamente, hai tre alternative se vuoi un'esecuzione differita:

  • Fallo in questo modo - esponi un IQueryable.
  • Implementare una struttura che esponga metodi specifici per filtri specifici o "domande". ( GetCustomersInAlaskaWithChildren, sotto)
  • Implementare una struttura che esponga un'API di filtro / ordinamento / pagina fortemente tipizzata e costruisca IQueryableinternamente.

Preferisco il terzo (anche se ho aiutato i colleghi a implementare anche il secondo), ma ovviamente ci sono alcune impostazioni e manutenzioni. (T4 in soccorso!)

Modifica : per chiarire la confusione che circonda ciò di cui sto parlando, considera il seguente esempio rubato da IQueryable può uccidere il tuo cane, rubare tua moglie, uccidere la tua volontà di vivere, ecc.

Nello scenario in cui esporresti qualcosa del genere:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

l'API di cui sto parlando ti permetterebbe di esporre un metodo che ti consentirebbe di esprimere GetCustomersInAlaskaWithChildren(o qualsiasi altra combinazione di criteri) in modo flessibile, e il repository lo eseguirà come IQueryable e ti restituirebbe i risultati. L'importante è che funzioni all'interno del livello repository, quindi sfrutta ancora l'esecuzione differita. Una volta ottenuti i risultati, puoi ancora LINQ-to-object su di esso a tuo piacimento.

Un altro vantaggio di un approccio come questo è che, poiché sono classi POCO, questo repository può vivere dietro un servizio web o WCF; può essere esposto ad AJAX o ad altri chiamanti che non conoscono la prima cosa su LINQ.


4
Quindi costruiresti un'API di query per sostituire l'API di query integrata di .NET?
Michael Brown,

4
Mi sembra abbastanza inutile
ozz,

7
@MikeBrown Il punto di questa domanda - e la mia risposta - è che si desidera esporre il comportamento o le vantaggi di IQueryable senza esporre il IQueryable . Come hai intenzione di farlo, con l'API integrata?
GalacticCowboy

2
È un'ottima tautologia. Non hai spiegato perché vuoi evitarlo. Heck WCF Data Services espone IQueryable attraverso il filo. Non sto cercando di prenderti cura di te, semplicemente non capisco questa avversione per le persone che sembrano avere.
Michael Brown,

2
Se hai intenzione di usare un IQueryable, allora perché dovresti usare un repository? I repository hanno lo scopo di nascondere i dettagli di implementazione. (Inoltre, WCF Data Services è un po 'più recente rispetto alla maggior parte delle soluzioni a cui ho lavorato negli ultimi 4 anni che hanno utilizzato un repository come questo ... Quindi non è probabile che vengano aggiornati in qualunque momento presto solo per rendere uso di un nuovo giocattolo luccicante).
GalacticCowboy

8

Esiste davvero una sola risposta legittima: dipende da come deve essere utilizzato il repository.

Ad un estremo il tuo repository è un wrapper molto sottile attorno a DBContext in modo da poter iniettare un'impiallacciatura di testabilità attorno a un'app basata su database. Non c'è davvero nessuna aspettativa nel mondo reale che questo possa essere usato in modo disconnesso senza un DB compatibile LINQ dietro di esso perché la tua app CRUD non ne avrà mai bisogno. Quindi certo, perché non usare IQueryable? Probabilmente preferirei IEnumarable poiché ottieni la maggior parte dei vantaggi [leggi: esecuzione ritardata] e non sembra così sporco.

A meno che non ci si trovi a quell'estremo, farei del mio meglio per sfruttare lo spirito del modello di repository e restituire raccolte materializzate appropriate che non hanno implicazioni su una connessione al database sottostante.


6
Per quanto riguarda il ritorno IEnumerable, è importante notare che ((IEnumerable<T>)query).LastOrDefault()è molto diverso da query.LastOrDefault()(presumere che querysia un IQueryable). Quindi, ha senso tornare solo IEnumerablese hai intenzione di scorrere tutti gli elementi comunque.
Lou

7
 > Should Repositories return IQueryable?

NO se si desidera eseguire test / sviluppo test-driven perché non esiste un modo semplice per creare un repository finto per testare il proprio businesslogic (= repository-consumer) in isolamento dal database o da altri provider IQueryable.


6

Dal punto di vista dell'architettura pura, IQueryable è un'astrazione che perde. Come generalità, fornisce all'utente troppa potenza. Detto questo, ci sono posti dove ha senso. Se si utilizza OData, IQueryable rende estremamente semplice fornire un endpoint facilmente filtrabile, ordinabile, raggruppabile ... ecc. In realtà preferisco creare DTO su cui le mie entità eseguono il mapping e IQueryable restituisce invece DTO. In questo modo pre-filtro i miei dati e utilizzo davvero solo il metodo IQueryable per consentire la personalizzazione dei risultati da parte del cliente.


6

Ho affrontato anche questa scelta.

Quindi, riassumiamo i lati positivi e negativi:

Positivi:

  • Flessibilità. È sicuramente flessibile e conveniente consentire al lato client di creare query personalizzate con un solo metodo di repository

Aspetti negativi:

  • Non testabile . (testerai l'implementazione IQueryable sottostante, che di solito è il tuo ORM. Ma dovresti invece testare la tua logica)
  • Sfoca la responsabilità . È impossibile dire cosa fa quel particolare metodo del tuo repository. In realtà, si tratta di un metodo divino, che può restituire qualsiasi cosa ti piaccia nell'intero database
  • Insicuro . Senza restrizioni su ciò che può essere richiesto da questo metodo di repository, in alcuni casi tali implementazioni possono non essere sicure dal punto di sicurezza.
  • Problemi di memorizzazione nella cache (o, per essere generici, eventuali problemi di pre o post elaborazione). In genere non è saggio, ad esempio, memorizzare nella cache tutto nella propria applicazione. Tenterai di memorizzare nella cache qualcosa che causerà un carico significativo al tuo database. Ma come fare, se si dispone di un solo metodo di repository, che i client utilizzano per inviare praticamente qualsiasi query sul database?
  • Problemi di registrazione . La registrazione di qualcosa a livello di repository ti farà ispezionare prima IQueryable per verificare se questa chiamata particolare viene registrata o meno.

Consiglierei di non utilizzare questo approccio. Prendi invece in considerazione la creazione di diverse specifiche e implementa metodi di repository, che possono interrogare il database usando quelli. Il modello di specifica è un ottimo modo per incapsulare un insieme di condizioni.


5

Penso che esporre l'IQueryable sul tuo repository sia perfettamente accettabile durante le fasi iniziali dello sviluppo. Esistono strumenti dell'interfaccia utente che possono funzionare direttamente con IQueryable e gestire ordinamento, filtro, raggruppamento, ecc.

Detto questo, penso che esporre il greggio sul repository e chiamarlo un giorno sia irresponsabile. Avere operazioni utili e helper di query per query comuni consente di centralizzare la logica di una query esponendo allo stesso tempo una superficie di interfaccia più piccola agli utenti del repository.

Per le prime iterazioni di un progetto, esporre pubblicamente IQueryable sul repository consente una crescita più rapida. Prima della prima versione completa, consiglierei di rendere l'operazione di query privata o protetta e di portare le query jolly sotto lo stesso tetto.


4

Quello che ho visto fare (e quello che sto implementando da solo) è il seguente:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

Ciò che ti consente di fare è avere la flessibilità di fare una IQueryable.Where(a => a.SomeFilter)query sul tuo repo facendo concreteRepo.GetAll(a => a.SomeFilter)senza esporre alcun business LINQy.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.