Sono un repository, anche se mi sono allontanato da schemi di repository generici. Al contrario, allinea i miei repository con la funzione aziendale che svolgono. I repository non mirano a sottrarre l'ORM, in quanto questo non è qualcosa che mi aspetto di cambiare, e allo stesso tempo evito di rendere un repository troppo granulare. (Vale a dire CRUD) Invece i miei repository servono da due a tre scopi chiave:
- Recupero dei dati
- Creazione dei dati
- Dura cancellazione
Per il recupero dei dati, il repository restituisce sempre IQueryable<TEntity>
. Per la creazione dei dati restituisce TEntity. Il repository gestisce il mio filtro a livello di base come lo stato "attivo" di autorizzazione per i sistemi che utilizzano schemi di eliminazione graduale e lo stato "corrente" per i sistemi che utilizzano dati storici. La creazione dei dati è responsabile solo per garantire che i riferimenti richiesti siano risolti e associati e che l'entità sia impostata e pronta per l'uso.
L'aggiornamento dei dati è di responsabilità della logica aziendale che lavora con le entità in questione. Questo può includere cose come le regole di validazione. Non provo a incapsularlo in un metodo di repository.
L'eliminazione nella maggior parte dei miei sistemi è soft-delete, quindi rientrerebbe nell'aggiornamento dei dati. (IsActive = false) Nel caso di cancellazioni forzate, questo sarebbe un one-liner nel repository.
Perché repository? Test-capacità. Certo, DbContext può essere deriso, ma è più semplice deridere una classe che ritornaIQueryable<TEntity>
. Funzionano bene anche con il modello UoW, personalmente uso il modello DbContextScope di Mehdime per definire l'unità di lavoro al livello desiderato (Ie Controller in MVC) e consentire ai miei controller e alle classi di servizio di supporto di utilizzare i repository senza dover passare riferimenti a UoW / dbContext in giro. L'uso di IQueryable significa che non hai bisogno di molti metodi wrapper nel repository e il tuo codice può ottimizzare il modo in cui i dati verranno consumati. Ad esempio, il repository non ha bisogno di esporre metodi come "Esiste" o "Conta" o provare a racchiudere le entità con altri POCO nei casi in cui si desidera sottoinsiemi di dati. Non è nemmeno necessario gestire le opzioni di caricamento desideroso per i dati correlati che potrebbero essere necessari o meno. Passando IQueryable, il codice chiamante può:
.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()
Molto flessibile e da un PoV di prova il mio repository beffardo deve semplicemente restituire Elenchi di oggetti di entità popolata.
Per quanto riguarda i repository generici, mi sono allontanato da questi per la maggior parte perché quando si finisce con un repository per tabella i controller / servizi finiscono con riferimenti a diversi repository per eseguire un'azione aziendale. Nella maggior parte dei casi solo uno o due di questi repository eseguono effettivamente operazioni di scrittura (a condizione che si stiano utilizzando correttamente le proprietà di navigazione) mentre il resto supporta quelli con Reads. Preferirei avere qualcosa come un deposito ordini in grado di leggere e creare ordini e leggere eventuali ricerche pertinenti ecc. (Oggetti cliente leggeri, prodotti, ecc.) Come riferimento durante la creazione di un ordine, piuttosto che colpire 5 o 6 repository diversi. Potrebbe violare i puristi di DNRY, ma la mia tesi è che lo scopo del repository è quello di servire la creazione di ordini che includa i relativi riferimenti.Repository<Product>
per ottenere prodotti dove per la base di un ordine ho solo bisogno di un'entità con una manciata di campi. Il mio OrderRepository potrebbe avere un .GetProducts()
metodo di ritorno IQueryable<ProductSummary>
che trovo migliore di uno Repository<Product>
che finisce per avere diversi metodi "Get" per provare a servire diverse aree delle esigenze dell'applicazione e / o qualche espressione di filtro pass-in complessa.
Opto per un codice più semplice che è facile da seguire, testare e ottimizzare. Può essere maltrattato, ma preferirei avere qualcosa in cui gli abusi siano facili da individuare e correggere piuttosto che provare a "bloccare" il codice in un modo che non può essere abusato, fallire e quindi avere qualcosa che sia un incubo per arrivare effettivamente a fare ciò che il cliente paga per farlo alla fine. :)