Sono pro-repository, anche se mi sono allontanato dai modelli di repository generici. Invece allineare i miei repository con la funzione aziendale che servono. I repository non sono finalizzati ad astrarre via l'ORM, in quanto non è qualcosa che mi aspetto di cambiare, e allo stesso tempo evito di rendere un repository troppo granulare. (I.e CRUD) Invece i miei repository servono da due a tre scopi chiave:
- Recupero dati
- Creazione dati
- Cancellazione difficile
Per il recupero dei dati, il repository restituisce sempre IQueryable<TEntity>
. Per la creazione dei dati restituisce TEntity. Il repository gestisce il mio filtro di livello base come lo stato di "attivazione" dell'autorizzazione per i sistemi che utilizzano pattern di eliminazione software 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 configurata e pronta per l'uso.
L'aggiornamento dei dati è responsabilità della logica aziendale che funziona con le entità in questione. Questo può includere cose come le regole di validazione. Non cerco di incapsularlo in un metodo di repository.
La cancellazione nella maggior parte dei miei sistemi è soft-delete quindi ricadrebbe sotto l'aggiornamento dei dati. (IsActive = false) Nel caso delle eliminazioni definitive questo sarebbe un one-liner nel repository.
Perché repository? Test-capacità. Certo, DbContext può essere deriso, ma è più semplice prendere in giro una classe che restituisce IQueryable<TEntity>
. Inoltre suonano bene con il pattern UoW, personalmente utilizzo il pattern DbContextScope di Mehdime per estendere l'unità di lavoro al livello desiderato (Ie Controller in MVC) e consentire ai miei controller e classi di servizio helper di utilizzare i repository senza dover passare i riferimenti a il UoW / dbContext in giro. L'utilizzo di IQueryable significa che non è necessario utilizzare molti metodi wrapper nel repository e il codice può ottimizzare il modo in cui i dati verranno utilizzati. Ad esempio il repository non ha bisogno di esporre metodi come "Exists" o "Count", o tenta di avvolgere le entità con altri POCO nei casi in cui si vogliono sottogruppi di dati. Non hanno nemmeno bisogno di gestire le opzioni di caricamento impaziente per i dati correlati di cui potresti avere bisogno 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 test P.o.V. il mio repository mocked deve semplicemente restituire liste di oggetti entità popolate.
Come per i repository generici, mi sono spostato 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 stanno effettivamente facendo operazioni di scrittura (a condizione che si stiano utilizzando le proprietà di navigazione correttamente) mentre il resto supporta quelli con Reads. Preferirei avere qualcosa come un repository di ordini che sia in grado di leggere e creare ordini e leggere qualsiasi ricerca pertinente, ecc. (Oggetti cliente leggeri, prodotti, ecc.) Come riferimento durante la creazione di un ordine, piuttosto che colpire 5 o 6 diversi repository. Potrebbe violare i puristi di DNRY, ma il mio argomento è che lo scopo del repository è quello di servire la creazione di ordini che include i riferimenti correlati. Non ho bisogno di andare a un 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 metodo .GetProducts()
che restituisce IQueryable<ProductSummary>
che trovo più bello di un Repository<Product>
che finisce con diversi metodi "Get" per provare e servire diverse aree delle esigenze dell'applicazione e / o qualche pass-in complesso espressione filtrante.
Scelgo un codice più semplice che sia facile da seguire, testare e ottimizzare. Può essere abusato in modi sbagliati, ma preferirei avere qualcosa in cui gli abusi siano facili da individuare e correggere piuttosto che cercare di "bloccare" il codice in un modo che non può essere abusato, fallire e poi avere qualcosa che è un incubo per arrivare a fare effettivamente ciò che il cliente paga per farlo fare alla fine. :)