Perché non dovrei usare il modello di repository con Entity Framework?


203

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ì?


60
era una domanda
trabocchetto

2
Avrei probabilmente risposto all'intervistatore che Microsoft usa molto spesso il modello di repository mentre dimostrano il framework delle entità: | .
Laurent Bourgault-Roy,

1
Qual è stata la ragione dell'intervistatore per non essere una buona idea?
Bob Horn,

3
Il fatto curioso è che la ricerca di "pattern di repository" in Google fornisce i risultati che sono principalmente correlati a Entity Framework e come utilizzare il pattern con EF.
Arseni Mourzenko,

2
controlla il blog di ayende ayende.com/blog . Basandosi su quello che so, usava il Repository Pattern ma alla fine lo abbandonò in favore del Query Object Pattern
Jaime Sangcap,

Risposte:


99

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 DbContextcon un repository è la testabilità dell'unità . Puoi avere la tua IRepositoryinterfaccia con 2 implementazioni, una (il vero repository) che usa DbContextper parlare con il database e la seconda, FakeRepositoryche può restituire oggetti in memoria / dati derisi. Questo rende l' IRepositoryunità 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
  }    
}

3
Non ho detto che non funzionerà, ho anche lavorato con il modello di repository con EF, ma oggi mi è stato chiesto perché NON È BUONO utilizzare il modello con DataBase, l'applicazione che utilizza Database

2
Ok, dato che questa è la risposta più popolare la sceglierò come risposta corretta

65
Quando è stata l'ultima volta che il più popolare == corretto?
HDave

14
DbContext è già un repository, il repository è pensato per essere un'astrazione di basso livello. Se si desidera astrarre origini dati diverse, creare oggetti per rappresentarli.
Daniel Little,

7
ColacX. Abbiamo provato proprio questo - DBcontext proprio nel livello del controller - e stiamo tornando al modello di pronti contro termine. Con il modello Repo, i Test delle unità sono passati da un massiccio derisione di DbContext che ha costantemente fallito. L'EF era difficile da usare, fragile e costava ore di ricerca per le sfumature dell'EF. Ora abbiamo piccole e semplici derisioni del repository. Il codice è più pulito. La separazione del lavoro è più chiara. Non sono più d'accordo con il pubblico sul fatto che EF sia già un modello di pronti contro termine e già testabile in unità.
Rhyous,

435

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.


68
Questa sembra essere l'unica risposta corretta.
Mike Chamberlain,

10
È possibile prendere in giro DbContexta EF6 + (vedi: msdn.microsoft.com/en-us/data/dn314429.aspx ). Anche nelle versioni minori, è possibile utilizzare un falso DbContextclasse -come con deriso DbSets, in quanto DbSetimplementa un iterface, IDbSet.
Chris Pratt,

14
@TheZenker, potresti non aver seguito esattamente il modello di repository. La differenza più rigorosa è il valore di ritorno. I repository restituiscono gli interrogabili, mentre i servizi dovrebbero restituire gli enumerabili. Anche quello non è davvero così bianco e nero, poiché c'è qualche sovrapposizione lì. È più nel modo in cui lo usi. Un repository dovrebbe semplicemente restituire l'insieme di tutti gli oggetti, in cui è necessario eseguire ulteriori query, mentre il servizio dovrebbe restituire il set di dati finale e non dovrebbe supportare ulteriori query.
Chris Pratt,

10
A rischio di sembrare egoistici: si sbagliano. Ora, per quanto riguarda i tutorial ufficiali, Microsoft ha fatto un passo indietro usando i repository da quello che ho visto dall'EF6. Per quanto riguarda il libro, non posso parlare del perché l'autore abbia scelto di utilizzare i repository. Quello di cui posso parlare, dato che qualcuno nelle trincee che costruiscono applicazioni su larga scala, è che l'uso del modello di repository con Entity Framework è un incubo per la manutenzione. Una volta che ti trasferisci in qualcosa di più complesso di una manciata di repository, finisci per spendere quantità esorbitanti di tempo nella gestione dei repository / unità di lavoro.
Chris Pratt,

6
Di solito ho solo un servizio per database o metodo di accesso. Uso metodi generici per interrogare più tipi di entità dallo stesso insieme di metodi. Uso Ninject per iniettare il mio contesto nel mio servizio e quindi il mio servizio nei miei controller in modo che tutto sia pulito e ordinato.
Chris Pratt,

45

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.


3
Qualsiasi articolo di architettura senza menzionare il test unitario viene automaticamente inviato al cestino per me. Uno dei punti del modello di repository è acquisire alcune abilità di test.
Sleeper Smith,

3
Puoi comunque effettuare unit test senza includere il contesto EF (che è già un repository). Dovresti testare il tuo dominio / i tuoi servizi e non le query del database (sono test di integrazione).
Daniel Little,

2
La testabilità di EF è notevolmente migliorata nella versione 6. Ora puoi completamente deridere 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 DbSetproprietà (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.
Chris Pratt,

Perdere la navigazione dell'entità correlata è male ed è contraria alla OOP, ma avrai un maggiore controllo su ciò che viene richiesto.
Alireza,

Per quanto riguarda i test, EF Core ha fatto molta strada con gli In-Memory e In-Memory out-of-box con i provider Sqlite per consentire i test unitari. Porta la finestra mobile quando hai bisogno di test di integrazione per eseguire test su un database containerizzato.
Sudhanshu Mishra,

16

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.


potresti per favore dire alcuni dei vantaggi di "Entity Framework ti offre un sacco di vantaggi di codifica e funzionali"?
ManirajSS,

2
questo è ciò che intendeva dire. var id = Entity.Where (i => i.Id == 1337) .Single () incapsulato e racchiuso questo in un repository in pratica non puoi fare una logica di query come questa dall'esterno, che ti costringe ad aggiungere più codice a il repository e l'interfaccia per il recupero dell'ID. B restituisce il contesto dell'entità dal repository in modo da poter scrivere la logica della query (che è solo una sciocchezza)
ColacX

14

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.

Riconsiderare il modello di repository


L'articolo mi è piaciuto, ma IMHO per le app aziendali, il livello di astrazione tra DAL e Bl DEVE avere una funzione, dal momento che non si poteva sapere cosa verrà utilizzato esattamente domani. Ma grazie per aver condiviso il link

1
Mentre personalmente penso che sia vero per esempio NHibernate ( ISessionFactorye ISessionsono facilmente derisibili) DbContext, purtroppo non è così facile ...
Patryk Ćwiek,

6

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.


Sono d'accordo con questa risposta. I miei repository sono fondamentalmente solo metodi di estensione, che non fanno altro che costruire alberi di espressioni. Oltre un'astrazione MOLTO semplice che fornisce semplicemente funzionalità generiche direttamente sopra la parte superiore di dbcontext. L'unico vero scopo dell'astrazione è rendere l'IoC un po 'più semplice. Penso che le persone provino a fare cose nei loro repository che non dovrebbero fare. Effettuano repository per entità o inseriscono la logica aziendale che dovrebbe trovarsi nel livello dei servizi. In realtà hai sempre solo bisogno di un semplice repository generico. Non è necessario, fornisce solo un'interfaccia coerente.
Brandon,

Un'altra cosa che volevo solo aggiungere. Sì, CQRS è una metodologia di gran lunga superiore nella maggior parte dei casi. Per alcuni clienti in cui ho lavorato quando i ragazzi del database non lavorano bene con gli sviluppatori (cosa che accade più spesso di quanto si pensi in particolare alle banche), EF over SQL è l'opzione migliore. In quello scenario specifico, quando non si ha assolutamente alcun controllo sul database, il modello di repository ha senso. Perché ricorda da vicino la struttura dei dati ed è facile tradurre ciò che accade nel database e viceversa. È davvero una decisione politica e logistica secondo me. Per placare gli dei DB.
Brandon,

1
In realtà sto iniziando a mettere in discussione le mie precedenti opinioni su questo. EF è un modello combinato di unità di lavoro e deposito. Come Chris Pratt ha menzionato sopra con EF6, puoi facilmente deridere gli oggetti Context e DbSet. Credo ancora che l'accesso ai dati dovrebbe essere racchiuso in classi per proteggere le classi di business logic dall'attuale meccanismo di accesso ai dati, ma per andare avanti e avvolgere EF con un altro repository e l'astrazione dell'Unità di lavoro sembra essere eccessiva.
James Culshaw,

Non penso che questa sia una buona risposta perché la tua dichiarazione di supporto è solo che ci sono numerosi vantaggi pur elencandone solo uno. Quello che fai non è un buon motivo perché puoi usare un database in memoria per fare test unitari.
Joel McBeth,

@jcmcbeth se guardi il mio commento direttamente sopra il tuo vedrai che ho cambiato la mia opinione originale riguardo al modello di archivio e EF.
James Culshaw,

0

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.


Ho riscontrato questo problema in diversi progetti diversi.
ColacX,

0

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.


1
Tranne il fatto che NON si desidera testare il comportamento del database in unit test, in quanto non è completamente quel livello di test.
Mariusz Jamro,

Sì, quello di cui stai parlando qui è il test di integrazione , ed è davvero prezioso, ma i test unitari sono totalmente diversi. I test unitari non dovrebbero mai raggiungere un vero database, ma si consiglia di aggiungere test di integrazione che lo facciano.
Chris Pratt,

-3

È 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!

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.