Esiste un reale vantaggio per il deposito generico?

26

Leggevo alcuni articoli sui vantaggi della creazione di repository generici per una nuova app ( esempio ). L'idea sembra carina perché mi permette di usare lo stesso repository per fare diverse cose contemporaneamente per diversi tipi di entità:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

Tuttavia, il resto dell'implementazione del repository ha un strong affidamento su Linq:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

Questo modello di repository ha funzionato in modo fantastico per Entity Framework e praticamente ha offerto una mappatura 1 a 1 dei metodi disponibili su DbContext / DbSet. Ma dato il lento assorbimento di Linq su altre tecnologie di accesso ai dati al di fuori di Entity Framework, quale vantaggio offre oltre a lavorare direttamente con DbContext?

Ho tentato di scrivere una versione PetaPoco del repository, ma PetaPoco non supporta Linq Expressions, il che rende la creazione di un l'interfaccia IRepository generica è praticamente inutile a meno che non la usi solo per i metodi base GetAll, GetById, Add, Update, Delete e Save e la utilizzi come classe base. Quindi devi creare repository specifici con metodi specializzati per gestire tutte le clausole "where" che potrei passare in precedenza come predicato.

Il modello di Repository generico è utile per qualcosa al di fuori di Entity Framework? In caso contrario, perché qualcuno dovrebbe usarlo invece di lavorare direttamente con Entity Framework?

Il link originale non riflette il pattern che stavo usando nel mio codice di esempio. Ecco un ( link aggiornato ) .

    
posta Sam 07.09.2012 - 02:34
fonte

6 risposte

33

Il repository generico è anche inutile (e IMHO anche male) per Entity Framework. Non porta alcun valore aggiuntivo a ciò che è già fornito da IDbSet<T> (che è btw. Repository generico).

Come hai già trovato l'argomento secondo cui il repository generico può essere sostituito dall'implementazione per altre tecnologie di accesso ai dati è piuttosto debole perché può richiedere di scrivere il proprio provider Linq.

Il secondo argomento comune sul test unitario semplificato è sbagliato anche perché repository di simulazione / impostato con la memoria dati in-memory sostituisce il provider Linq con un altro che ha capacità diverse. Il provider Linq-to-entities supporta solo sottoinsiemi di funzionalità Linq - anche non supporta tutti i metodi disponibili sull'interfaccia IQueryable<T> . La condivisione di alberi di espressioni tra livello di accesso ai dati e livelli di business logic impedisce qualsiasi falsificazione del livello di accesso ai dati - la logica delle query deve essere separata.

Se vuoi avere una strong astrazione "generica" devi coinvolgere anche altri modelli. In questo caso è necessario utilizzare un linguaggio di query astratto che possa essere tradotto dal repository in un linguaggio di query specifico supportato dal livello di accesso ai dati utilizzato. Questo è gestito dal modello di specifica. Linq su IQueryable è specifica (ma la traduzione richiede provider - o qualche visitatore personalizzato che traduce l'albero delle espressioni in query) ma puoi definire la tua versione semplificata e usarla. Ad esempio, NHibernate utilizza l'API Criteria. Ancora il modo più semplice è utilizzare repository specifici con metodi specifici. In questo modo è il più semplice da implementare, più semplice da testare e più semplice da simulare nei test unitari perché la logica della query è completamente nascosta e separata dall'astrazione.

    
risposta data 07.09.2012 - 11:46
fonte
7

Il problema non è il pattern del repository. Avere un'astrazione tra il recupero dei dati e il modo in cui lo si ottiene è una buona cosa.

Il problema qui è l'implementazione. Supponendo che un'espressione arbitraria possa funzionare per il filtraggio è azzardata nella migliore delle ipotesi.

Fare in modo che un repository funzioni per tutti i tuoi oggetti direttamente manca il punto. Gli oggetti dati raramente verranno mappati direttamente sugli oggetti business. Passare in T per filtrare ha molto meno senso in queste situazioni. E fornire così tanta funzionalità garantisce praticamente che non è possibile sostenerlo tutto quando arriva un fornitore diverso.

    
risposta data 07.09.2012 - 03:56
fonte
2

Il valore di un livello dati generico (un repository è un particolare tipo di livello dati) consente al codice di modificare il meccanismo di archiviazione sottostante con un impatto minimo o nullo sul codice chiamante.

In teoria, questo funziona bene. In pratica, come hai osservato, l'astrazione spesso perde. I meccanismi utilizzati per accedere ai dati in uno sono diversi dai meccanismi di un altro. In alcuni casi, si finisce per scrivere il codice due volte: una volta nel livello aziendale e ripetendolo nel livello dati.

Il modo più efficace per creare un livello dati generico è conoscere i diversi tipi di fonti di dati che l'applicazione utilizzerà in anticipo. Come hai visto, supponendo che LINQ o SQL siano universali può essere un problema. Provare ad aggiornare i nuovi archivi dati risulterà probabilmente in una riscrittura.

[Modifica: aggiunto il seguente.]

Dipende anche da cosa l'applicazione ha bisogno dal livello dati. Se l'applicazione sta solo caricando o archiviando oggetti, il livello dati può essere molto semplice. Poiché la necessità di cercare, ordinare e filtrare aumenta, la complessità del livello dati aumenta e le astrazioni iniziano a perdere spazio (come ad esempio l'esposizione delle query LINQ nella domanda). Una volta che gli utenti possono fornire le proprie query, tuttavia, il costo / beneficio del livello dati deve essere attentamente valutato.

    
risposta data 07.09.2012 - 03:41
fonte
1

Avere un livello di codice sopra il database è utile in quasi tutti i casi. Preferirei in genere un pattern "GetByXXXXX" in detto codice: consente di ottimizzare le query dietro di esso quando necessario, mantenendo l'interfaccia utente libera da clutter di interfaccia dati.

Sfruttare i generici è sicuramente un gioco equo - avere un metodo Load<T>(int id) ha un senso. Ma costruire repository attorno a LINQ è l'equivalente del 2010 di eliminare query SQL ovunque, con un po 'di sicurezza del tipo aggiunto.

    
risposta data 07.09.2012 - 02:50
fonte
0

Bene, con il link fornito posso vedere che può essere un utile wrapper per DataServiceContext , ma non riduce le manipolazioni del codice né migliora la leggibilità. Inoltre, l'accesso a DataServiceQuery<T> è ostruito, limitando la flessibilità a .Where() e .Single() . Né sono forniti AddRange() o alternative. Né è fornito Delete(Predicate) che potrebbe essere utile ( repo.Delete<Person>( p => p.Name=="Joe" ); per eliminare Joe-s). Ecc.

Conclusione: tale API ostruisce l'API nativa e la limita a poche semplici operazioni.

    
risposta data 07.09.2012 - 03:07
fonte
-2

Direi "Sì". A seconda del modello di dati, un repository generico ben sviluppato può rendere i dati più accessibili, più snelli e più robusti.

Leggi questa serie di articoli (da @ chris-pratt ):

risposta data 11.05.2017 - 23:33
fonte

Leggi altre domande sui tag