Gestire l'accesso ai dati direttamente all'interno della business logic non è una buona idea, poiché la diretta implementazione dei vari metodi CRUD va inevitabilmente a creare un forte accoppiamento tra due funzionalità che per loro natura sono differenti. Un approccio del genere causa una serie di svantaggi tra cui:
1. Difficile testabilità della business logic
2. Forte dipendenza da parte della business logic di compoenenti esterne come il database
3. Duplicazione del codice di accesso ai dati all'interno della codebase
4. Difficile implementazione di meccanismi multi-tier dedicati alla cache
In questo articolo vedremo come sia possibile implementare il Repository Pattern sfruttando i tipi generici in modo da risolvere i problemi sopra indicati. Questo approccio può essere utilizzato in tutti quei progetti dove è richiesto un layer, o comunque una serie di funzionalità, di accesso ai dati.
Creazione di un contratto comune per l'entità generica
Come prima cosa è necessario definire un contratto comune che successivamente tutti i nostri tipi concreti andranno a utilizzare, pertanto creiamo una nuova class EntityBase come segue.
namespace ASPItalia.RepositoryPattern { public class EntityBase { public int Id { get; set; } } }
Qualora utilizzassimo EF Code First, la proprietà Id rappresenterà il campo chiave della nostra identità.
Definizione del repository
Andiamo ora a definire tramite un'interfaccia, i vari metodi che il nostro oggetto repository dovrà implementare. Creiamo quindi un'interfaccia chiamata IRepository come segue:
namespace ASPItalia.RepositoryPattern { public interface IRepository<T> where T : EntityBase { IEnumerable<T> List { get; } Task Add(T entity); Task Delete(T entity); Task Update(T entity); Task<T> FindById(int Id); } }
Come possiamo vedere, abbiamo predisposto i vari metodi CRUD utilizzando il tipo di ritorno Task o Task<T> in modo da permettere un facile utilizzo delle versioni asincrone dei metodi di interrogazione esposti da EF.
Creazione di un modello
Creiamo ora una nuova classe, che idealmente rappresenterà un'entità all'interno del nostro database, in modo da renderla "consumabile" tramite la classe di repository che creeremo a breve. Creiamo una classe Customer come segue:
namespace ASPItalia.RepositoryPattern { public partial class Customer : EntityBase { [Required] public String Name { get; set; } } }
Creazione del repository per la classe Customer
Ora che abbiamo definito dei contratti comuni e un modello che rappresenta i nostri dati, andiamo a definire la classe che si occuperà di interrogare la nostra base di dati, restituendo i dati che la nostra business logic tratterà in qualche modo. Creiamo una classe CustomerRepository come segue.
namespace ASPItalia.RepositoryPattern { public class CustomerRepository : IRepository<Customer>, IDisposable { private ApplicationContext dbContext; public CustomerRepository() { dbContext = new ApplicationContext(); } public IEnumerable<Customer> List { get { return dbContext.Customers.ToList(); } } public async Task Add(Customer entity) { if(entity != null) { dbContext.Customers.Add(entity); await dbContext.SaveChangesAsync(); } } public async Task Delete(Customer entity) { if (entity != null) { dbContext.Customers.Remove(entity); await dbContext.SaveChangesAsync(); } } public async Task<Customer> FindById(int Id) { return await dbContext.Customers.FindAsync(Id); } public async Task Update(Customer entity) { dbContext.Entry(entity).State = EntityState.Modified; await dbContext.SaveChangesAsync(); } public void Dispose() { if(dbContext != null) { dbContext.Dispose(); } } } }
In aggiunta all'interfaccia IRepository<T> abbiamo anche implementato l'interfaccia IDisposable per migliorare la gestione della memoria.
Data la semplicità di questo approccio e la completa indipendenza della classe CustomerRepository dalla business logic, la successiva implementazione di una cache in-memory adesso risulta immediata.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Sfruttare lo stream rendering per le pagine statiche di Blazor 8
Specificare il versioning nel path degli URL in ASP.NET Web API
Usare un KeyedService di default in ASP.NET Core 8
Implementare il throttling in ASP.NET Core
Eseguire attività pianificate con Azure Container Jobs
Usare Refit e Polly in Blazor per creare client affidabili e fortemente tipizzati
Ottimizzare le performance delle collection con le classi FrozenSet e FrozenDictionary
Utilizzare politiche di resiliency con Azure Container App
Implementare l'infinite scroll con QuickGrid in Blazor Server
Creazione di plugin per Tailwind CSS: espandere le funzionalità del framework dinamicamente
Utilizzare Tailwind CSS all'interno di React: installazione
Code scanning e advanced security con Azure DevOps
I più letti di oggi
- Utilizzare WebAssembly con .NET, ovunque
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Ottimizzare le performance delle collection con le classi FrozenSet e FrozenDictionary
- Utilizzare il trigger SQL con le Azure Function
- Ottimizzazione dei block template in Angular 17
- Disabilitare automaticamente un workflow di GitHub (parte 2)