Durante un colloquio di lavoro, mi è stato chiesto di spiegare perché il modello di repository non è un buon modello per lavorare con ORM come Entity Framework. Perché è così?
Durante un colloquio di lavoro, mi è stato chiesto di spiegare perché il modello di repository non è un buon modello per lavorare con ORM come Entity Framework. Perché è così?
Risposte:
Non vedo alcun motivo per cui il modello di repository non funzioni con Entity Framework. Il modello di repository è un livello di astrazione inserito nel livello di accesso ai dati. Il livello di accesso ai dati può essere qualsiasi cosa, dalle semplici procedure memorizzate ADO.NET a Entity Framework o un file XML.
Nei sistemi di grandi dimensioni, in cui sono presenti dati provenienti da origini diverse (database / XML / servizio Web), è consigliabile disporre di un livello di astrazione. Il modello di repository funziona bene in questo scenario. Non credo che Entity Framework sia un'astrazione sufficiente per nascondere ciò che accade dietro le quinte.
Ho usato il modello di repository con Entity Framework come metodo del livello di accesso ai dati e devo ancora affrontare un problema.
Un altro vantaggio di astrarre il DbContext
con un repository è la testabilità dell'unità . Puoi avere la tua IRepository
interfaccia con 2 implementazioni, una (il vero repository) che usa DbContext
per parlare con il database e la seconda, FakeRepository
che può restituire oggetti in memoria / dati derisi. Questo rende l' IRepository
unità testabile, quindi altre parti di codice che utilizza IRepository
.
public interface IRepository
{
IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
private YourDbContext db;
private EFRepository()
{
db = new YourDbContext();
}
public IEnumerable<CustomerDto> GetCustomers()
{
return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
}
}
public MockRepository : IRepository
{
public IEnumerable<CustomerDto> GetCustomers()
{
// to do : return a mock list of Customers
// Or you may even use a mocking framework like Moq
}
}
Ora usando DI, ottieni l'implementazione
public class SomeService
{
IRepository repo;
public SomeService(IRepository repo)
{
this.repo = repo;
}
public void SomeMethod()
{
//use this.repo as needed
}
}
Il miglior motivo per non utilizzare il modello di repository con Entity Framework? Entity Framework implementa già un modello di repository. DbContext
è la tua UoW (unità di lavoro) e ognuno DbSet
è il repository. L'implementazione di un altro livello oltre a questo non è solo ridondante, ma rende più difficile la manutenzione.
Le persone seguono i modelli senza rendersi conto dello scopo del modello. Nel caso del modello di repository, lo scopo è quello di sottrarre la logica di query del database di basso livello. Ai vecchi tempi di scrivere effettivamente le istruzioni SQL nel codice, il modello di repository era un modo per spostare l'SQL da singoli metodi sparsi in tutta la base di codice e localizzarlo in un unico posto. Avere un ORM come Entity Framework, NHibernate, ecc è una sostituzione per questo codice astrazione, e come tale, elimina la necessità per il pattern.
Tuttavia, non è una cattiva idea creare un'astrazione in cima al tuo ORM, non è nulla di così complesso come UoW / repository. Andrei con un modello di servizio, in cui costruisci un'API che l'applicazione può utilizzare senza sapere o preoccuparsi se i dati provengono da Entity Framework, NHibernate o un'API Web. Questo è molto più semplice, poiché si aggiungono semplicemente metodi alla propria classe di servizio per restituire i dati richiesti dall'applicazione. Se stavi scrivendo un'app Da fare, ad esempio, potresti ricevere una chiamata di servizio per restituire gli articoli che sono in scadenza questa settimana e non sono ancora stati completati. Tutto ciò che la tua app sa è che se vuole queste informazioni, chiama quel metodo. All'interno di quel metodo e nel tuo servizio in generale, interagisci con Entity Framework o qualsiasi altra cosa tu stia utilizzando. Quindi, se in seguito decidi di cambiare ORM o estrarre le informazioni da un'API Web,
Può sembrare che sia un potenziale argomento per l'utilizzo del modello di repository, ma la differenza chiave qui è che un servizio è un livello più sottile ed è orientato alla restituzione di dati completi, piuttosto che qualcosa su cui continui a interrogare, come con un repository.
DbContext
a EF6 + (vedi: msdn.microsoft.com/en-us/data/dn314429.aspx ). Anche nelle versioni minori, è possibile utilizzare un falso DbContext
classe -come con deriso DbSet
s, in quanto DbSet
implementa un iterface, IDbSet
.
Ecco un esempio di Ayende Rahien: Architecting in the pit of doom: The mali of the repository abstraction layer
Non sono ancora sicuro di essere d'accordo con la sua conclusione. È un fermo-22: da un lato, se avvolgo il mio contesto EF in repository specifici per tipo con metodi di recupero dati specifici per query, sono effettivamente in grado di testare il mio codice (sorta di), il che è quasi impossibile con Entity Quadro da solo. D'altra parte, perdo la capacità di eseguire query complesse e il mantenimento semantico delle relazioni (ma anche quando ho pieno accesso a quelle funzionalità mi sento sempre come se stessi camminando su gusci d'uovo intorno a EF o qualsiasi altro ORM che potrei scegliere , dal momento che non so mai quali metodi la sua implementazione di IQueryable potrebbe o non potrebbe supportare, se interpreterà la mia aggiunta a una raccolta di proprietà di navigazione come una creazione o semplicemente un'associazione, se si tratterà di un caricamento lento o desideroso o di non caricarlo affatto impostazione predefinita, ecc., quindi forse questo è per il meglio. La "mappatura" relazionale ad oggetti a impedenza zero è qualcosa di una creatura mitologica - forse è per questo che l'ultima versione di Entity Framework è stata denominata in codice "Magic Unicorn").
Tuttavia, il recupero delle entità mediante metodi di recupero dei dati specifici della query significa che i test delle unità ora sono essenzialmente test in white box e non si ha scelta in merito, dal momento che è necessario conoscere in anticipo esattamente quale metodo di repository verrà sottoposto all'unità in prova chiama per deriderlo. E non stai ancora testando le query stesse, a meno che tu non scriva anche test di integrazione.
Questi sono problemi complessi che richiedono una soluzione complessa. Non puoi risolverlo semplicemente fingendo che tutte le tue entità siano tipi separati senza relazioni tra loro e atomizzarle ognuna nel proprio repository. Beh, puoi , ma fa schifo.
Aggiornamento: ho avuto qualche successo usando il provider Effort per Entity Framework. Effort è un provider in memoria (open source) che ti consente di utilizzare EF nei test esattamente come lo utilizzeresti su un vero database. Sto pensando di cambiare tutti i test in questo progetto, sto lavorando per utilizzare questo provider, poiché sembra rendere le cose molto più facili. È l'unica soluzione che ho trovato finora che affronta tutti i problemi di cui mi occupavo in precedenza. L'unica cosa è che c'è un leggero ritardo all'avvio dei miei test mentre sta creando il database in memoria (usa un altro pacchetto chiamato NMemory per farlo), ma non lo vedo come un vero problema. C'è un articolo di Code Project che parla dell'utilizzo di Effort (contro SQL CE) per i test.
DbContext
. Indipendentemente da ciò, potresti sempre deridere DbSet
, e questa è comunque la carne di Entity Framework. DbContext
è poco più di una classe per ospitare le DbSet
proprietà (repository) in un'unica posizione (unità di lavoro), in particolare in un contesto di test di unità, in cui tutte le informazioni di inizializzazione e connessione del database non sono comunque richieste o necessarie.
Il motivo per cui probabilmente lo faresti è perché è un po 'ridondante. Entity Framework ti offre numerosi vantaggi di codifica e funzionali, ecco perché lo usi, se poi lo prendi e lo avvolgi in un modello di repository che stai buttando via quei vantaggi, potresti anche usare qualsiasi altro livello di accesso ai dati.
In teoria, penso che abbia senso incapsulare la logica di connessione al database per renderla più facilmente riutilizzabile, ma come sostiene il link in basso, i nostri quadri moderni si occupano essenzialmente di questo adesso.
ISessionFactory
e ISession
sono facilmente derisibili) DbContext
, purtroppo non è così facile ...
Un ottimo motivo per utilizzare il modello di repository è consentire la separazione della logica aziendale e / o dell'interfaccia utente da System.Data.Entity. Ci sono numerosi vantaggi in questo, inclusi i reali vantaggi nei test unitari che consentono l'uso di falsi o simulazioni.
Abbiamo avuto problemi con duplicati ma differenti istanze DbContext di Entity Framework quando un contenitore IoC che caricava nuovi () archivi per tipo (ad esempio un UserRepository e un'istanza GroupRepository che ciascuno chiama il proprio IDbSet da DBContext), a volte può causare più contesti per richiesta (in un contesto MVC / web).
Il più delle volte funziona ancora, ma quando si aggiunge un livello di servizio in aggiunta a quello e quei servizi presuppongono che gli oggetti creati con un contesto vengano correttamente collegati come raccolte figlio a un nuovo oggetto in un altro contesto, a volte fallisce e a volte no t a seconda della velocità dei commit.
Dopo aver provato il modello di repository su un piccolo progetto, consiglio vivamente di non usarlo; non perché complica il tuo sistema, e non perché i dati beffardi sono un incubo, ma perché i tuoi test diventano inutili !!
I dati beffardi consentono di aggiungere dettagli senza intestazioni, aggiungere record che violano i vincoli del database e rimuovere entità che il database rifiuta di rimuovere. Nel mondo reale un singolo aggiornamento può influire su più tabelle, registri, cronologia, riepiloghi, ecc., Nonché colonne come il campo della data dell'ultima modifica, chiavi generate automaticamente, campi calcolati.
In breve, l'esecuzione del test su un database reale ti dà risultati reali e puoi testare non solo i tuoi servizi e le tue interfacce, ma anche il comportamento del database. Puoi verificare se le tue procedure memorizzate fanno la cosa giusta con i dati, restituiscono il risultato atteso o se il record che hai inviato per cancellare è davvero cancellato! Tali test possono anche esporre problemi come dimenticare di sollevare errori dalla procedura memorizzata e migliaia di tali scenari.
Penso che il framework delle entità implementa il modello di repository meglio di qualsiasi degli articoli che ho letto finora e va ben oltre ciò che stanno cercando di realizzare.
Il repository era la migliore pratica in quei giorni in cui stavamo usando XBase, AdoX e Ado.Net, ma con entità !! (Repository over repository)
Infine, penso che troppe persone investano molto tempo nell'apprendimento e nell'implementazione del modello di deposito e si rifiutano di lasciarlo andare. Principalmente per dimostrare a se stessi che non hanno perso tempo.
È dovuto alle migrazioni: non è possibile far funzionare le migrazioni, poiché la stringa di connessione risiede in web.config. Ma, DbContext risiede nel livello Repository. IDbContextFactory deve avere una stringa di configurazione nel database. Ma non è possibile che le migrazioni ottengano la stringa di connessione da web.config.
Ci sono soluzioni ma non ho ancora trovato una soluzione pulita per questo!