I repository dovrebbero restituire IQueryable?

60

Ho visto molti progetti con repository che restituiscono istanze di IQueryable . Ciò consente di eseguire ulteriori filtri e ordinamenti su IQueryable da un altro codice, che si traduce in diversi SQL generati. Sono curioso di sapere da dove viene questo schema e se sia una buona idea.

La mia più grande preoccupazione è che un IQueryable è una promessa di colpire il database qualche tempo dopo, quando viene enumerato. 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.

Ho anche avuto problemi con i Multiple Active Result Sets (MARS) in passato (specialmente quando si usano le transazioni) e questo approccio sembra che ciò potrebbe accadere più spesso.

Ho sempre chiamato AsEnumerable o ToArray alla fine di ciascuna delle mie espressioni LINQ per assicurarmi che il database venga colpito prima di lasciare il codice del repository.

Mi chiedo se restituire IQueryable potrebbe essere utile come blocco di base per un livello dati. Ho visto un codice piuttosto stravagante con un repository che chiama un altro repository per creare un IQueryable ancora maggiore.

    
posta Travis Parks 26.03.2013 - 21:57
fonte

9 risposte

42

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

Dal lato positivo, non è necessario creare molti 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. (/ dovresti) dovresti definire i contratti comportamentali a cui le tue implementazioni aderiscono, ma in ogni caso ciò dipende da te.

Quindi potresti mettere la grintosa logica di recupero al di fuori del repository e lasciare che sia usata comunque dall'utente. Pertanto, l'esposizione di IQueryable offre la massima flessibilità e consente di eseguire query efficienti anziché filtraggi in-memory, ecc. E potrebbe ridurre la necessità di fare un sacco di metodi specifici per il recupero dei dati.

D'altra parte, ora hai dato ai tuoi utenti un fucile. Possono fare cose che potreste non volere (usare eccessivamente .include (), fare query pesanti e fare filtri in memoria nelle loro rispettive implementazioni, ecc.), Che fondamentalmente farebbe un passo laterale al layering e controlli comportamentali perché hai dato pieno accesso.

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

    
risposta data 26.03.2013 - 22:34
fonte
26

Esporre IQueryable alle interfacce pubbliche non è una buona pratica. La ragione è fondamentale: non è possibile fornire una realizzazione IQueryable come si dice che sia.

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

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

Il modello del repository ci fornisce una rappresentazione chiara mediante stratificazione e, cosa più importante, indipendenza di realizzazione. Quindi possiamo cambiare nella futura fonte di dati.

La domanda è " Dovrebbe esserci possibilità di sostituzione dell'origine dati? ".

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

( altri buoni punti sul perché questa pratica è cattiva)

    
risposta data 16.12.2013 - 20:40
fonte
18

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

  • Esegui in questo modo - esporre un IQueryable .
  • Implementa una struttura che espone metodi specifici per filtri specifici o "domande". ( GetCustomersInAlaskaWithChildren , sotto)
  • Implementa una struttura che esponga un'API filtro / ordinamento / pagina strongmente tipizzata e crea internamente IQueryable .

Preferisco il terzo (anche se ho aiutato i colleghi a implementare anche il secondo), ma ovviamente c'è un po 'di installazione e manutenzione. (T4 in soccorso!)

Modifica : per chiarire la confusione che circonda ciò di cui sto parlando, considera il seguente esempio rubato da IQueryable Can Kill Your Dog , Ruba la tua moglie, uccidi 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 consentirebbe di esporre un metodo che ti permetta di esprimere GetCustomersInAlaskaWithChildren (o qualsiasi altra combinazione di criteri) in maniera flessibile, e il repository lo eseguirà come IQueryable e restituirà i risultati a te. Ma l'importante è che funzioni all'interno del livello di repository, quindi sfrutta ancora l'esecuzione differita. Una volta che i risultati sono tornati, puoi ancora LINQ-to-objects su di esso per il contenuto del tuo cuore.

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.

    
risposta data 26.03.2013 - 22:28
fonte
8

Esiste solo una risposta legittima: dipende da come deve essere usato il repository.

A un estremo il tuo repository è un involucro molto sottile attorno a un DBContext in modo da poter iniettare un rivestimento di testabilità attorno a un'app basata su database. Non c'è davvero alcuna aspettativa del mondo reale che questo possa essere usato in modo disconnesso senza un DB LINQ amichevole dietro perché la tua app CRUD non ne avrà mai bisogno. Allora sicuro, perché non usare IQueryable? Probabilmente preferirei IEnumarable poichè ottieni la maggior parte dei benefici [leggi: esecuzione ritardata] e non mi sembra sporco.

A meno che tu non sia seduto a quell'estremo, cercherò di sfruttare lo spirito del modello di repository e restituire raccolte materializzate appropriate che non hanno implicazioni di una connessione di database sottostante.

    
risposta data 26.03.2013 - 22:23
fonte
5
 > Should Repositories return IQueryable?

NO se si desidera eseguire uno sviluppo / unittest testdriven perché non esiste un modo semplice per creare un repository fittizio per testare businesslogic (= repository-consumer) in isolamento dal database o da un altro provider IQueryable.

    
risposta data 21.01.2014 - 18:00
fonte
5

Da un punto di vista dell'architettura pura, IQueryable è un'astrazione che perde. In generale, fornisce all'utente troppa potenza. Detto questo, ci sono luoghi in cui ha senso. Se si utilizza OData, IQueryable rende estremamente facile fornire un endpoint che sia facilmente filtrabile, ordinabile, raggruppabile ... ecc. In realtà preferisco creare DTO su cui le mie entità si collegano e IQueryable restituisce il DTO. In questo modo pre-filtro i miei dati e in realtà utilizzo solo il metodo IQueryable per consentire la personalizzazione dei risultati da parte dei clienti.

    
risposta data 17.06.2015 - 21:42
fonte
4

Penso che l'esposizione di IQueryable nel tuo repository sia perfettamente accettabile durante le fasi iniziali di sviluppo. Esistono strumenti dell'interfaccia utente che possono funzionare direttamente con IQuery e gestiscono l'ordinamento, il filtro, il raggruppamento e così via.

Detto questo, penso che esponendo il repository e rinviandolo al giorno sia irresponsabile. Avere operazioni utili e aiutanti di query per le query comuni consente di centralizzare la logica per una query esponendo anche una superficie di interfaccia più piccola ai consumatori del repository.

Per le prime iterazioni di un progetto, l'esposizione di IQueryable pubblicamente 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 selvagge sotto lo stesso tetto.

    
risposta data 26.03.2013 - 22:33
fonte
4

Quello che ho visto fatto (e quello che sto implementando io stesso) è 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 permette di fare è avere la flessibilità di fare una query IQueryable.Where(a => a.SomeFilter) sul tuo repository facendo concreteRepo.GetAll(a => a.SomeFilter) senza esporre alcuna attività LINQy.

    
risposta data 21.01.2014 - 17:03
fonte
4

Ho affrontato anche questa scelta.

Quindi riassumiamo i lati positivi e negativi:

positivi:

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

Negativi:

  • non verificabile . (in realtà testerai l'implementazione IQueryable sottostante, che di solito è il tuo ORM, ma dovresti testare la tua logica)
  • Sfoca la responsabilità . È impossibile dire quale sia il particolare metodo del tuo repository. In realtà, è un metodo divino, che può restituire tutto ciò che ti piace nell'intero database
  • non sicura . Senza restrizioni su ciò che può essere interrogato da questo metodo di repository, in alcuni casi tali implementazioni possono essere non sicure dal punto di sicurezza.
  • Problemi di caching (o, per essere generici, eventuali problemi di pre-elaborazione o post-elaborazione). In genere non è saggio, ad esempio, memorizzare nella cache tutto nella propria applicazione. Proverai a mettere in cache qualcosa, che causa un carico significativo del tuo database. Ma come fare, se si dispone di un solo metodo di repository, che i client usano per pubblicare virtualmente qualsiasi query sul database?
  • Problemi di registrazione . La registrazione di qualcosa nel livello del repository ti consentirà di ispezionare IQueryable per verificare se questa particolare chiamata verrà registrata o meno.

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

    
risposta data 06.11.2015 - 15:54
fonte

Leggi altre domande sui tag