Implementare un Repository Pattern generico e asincrono in C#

di Giancarlo Lelli, in .NET Framework,

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi