MCardinale
MCardinale

Reputation: 1406

How to build a generic repository

I'm developing a web application in ASP.NET MVC with NHibernate.

Based in articles and tutorials I've found at Google, I'm using Repository for my classes.

I have 10 classes and 10 repositories. Today I figured out that 90% of mine repositories are exactly equal each other, except for the class. This is one example:

public class PromocaoRepository:IPromocaoRepository {
    private ISession Session;

    public PromocaoRepository() {
        this.Session = NHibernateSessionFactory.OpenSession();
    }

    public void Add(Promocao promocao) {
        using(ITransaction transaction = this.Session.BeginTransaction()) {
            this.Session.Save(promocao);
            transaction.Commit();
        }
    }

    public void Edit(Promocao promocao) {
        using(ITransaction transaction = this.Session.BeginTransaction()) {
            this.Session.Update(promocao);
            transaction.Commit();
        }
    }

    public void Remove(Promocao promocao) {
        using(ITransaction transaction = this.Session.BeginTransaction()) {
            this.Session.Delete(promocao);
            transaction.Commit();
        }
    }

    public Promocao GetById(int id) {
        return this.Session.Get<Promocao>(id);
    }

}

There is a way to do a kind of generic repository witch I can use in all my classes?

If it's possible, what should I do in case I need to create a particular method for an specific class?

Upvotes: 3

Views: 1923

Answers (5)

Ngola cttts
Ngola cttts

Reputation: 1

enter image description here

I have implemented a generic repository pattern in my project. My folder structure is as shown below:

Abstractions Folder:

Contains generic interfaces like IReadRepository and IWriteRepository, along with model-specific interfaces (e.g., ICategoryRepository, ITutorialRepository, etc.) where I can define model-specific methods if needed.

Concretes Folder:

Contains the generic repository implementations like ReadRepository and WriteRepository.

Additionally, I create specific repositories for each model (e.g., CategoryRepository, TutorialRepository), which inherit the generic repositories but also allow me to add model-specific methods.

My Approach:

Generic Implementation: The ReadRepository and WriteRepository classes handle basic CRUD operations for all models.

Model-Specific Repositories: If a model requires specific methods or logic, I add them in the respective repository (e.g., CategoryRepository).

This is my interface:

public interface IReadRepository<T> : IRepository<T> where T : 
    BaseEntity, new()
    {
     
     Task<IEnumerable<T>> GetAllAsync();
     Task<T?> GetByIdAsync(int id, bool isTracking, params string[] includes);
     Task<T?> GetSingleByConditionAsync(Expression<Func<T, bool>> condition);
     IQueryable<T> GetAllByConditionAsync(Expression<Func<T, bool>> condition);
     Task CreateAsync(T entity);
     void Update(T entity);
     void Delete(T entity);
     void DeleteRange(params T[] entities);
     Task<int> SaveChangeAsync();

 }

And this repository:

public class WriteRepository<T> : IWriteRepository<T> where T : 
    BaseEntity, new()
    {
     private readonly AppDbContext _context;

     public WriteRepository(AppDbContext context)
     {
         _context = context;
     }

     public DbSet<T> Table => _context.Set<T>();

     public async Task CreateAsync(T entity)
     {
         await Table.AddAsync(entity);
     }

     public void Delete(T entity)
     {
         Table.Remove(entity);
     }

     public void DeleteRange(params T[] entities)
     {
         Table.RemoveRange(entities);
     }


     public async Task<int> SaveChangeAsync()
     {
         int rows = await _context.SaveChangesAsync();
         return rows;
     }

     public void Update(T entity)
     {
         Table.Update(entity);
     }

     public async Task<T?> GetByIdAsync(int id, bool isTracking, params string[] includes)
     {
         IQueryable<T> query = Table.AsQueryable();

         if (!isTracking)
         {
             query = query.AsNoTracking();
         }

         if (includes.Length > 0)
         {
             foreach (string include in includes)
             {
                 query = query.Include(include);
             }
         }

         T? entity = await query.SingleOrDefaultAsync(e => e.Id == id);

         return entity;
     }

     public async Task<T?> GetSingleByConditionAsync(Expression<Func<T, bool>> condition)
     {
         IQueryable<T> query = Table.AsQueryable();

         T? entity = await query.SingleOrDefaultAsync(condition);
         return entity;
     }

     public IQueryable<T> GetAllByConditionAsync(Expression<Func<T, bool>> condition)
     {
         IQueryable<T> query = Table.AsQueryable();

         query = query.Where(condition);
         return query;
     }

     public async Task<IEnumerable<T>> GetAllAsync()
     {
         return await _context.Set<T>().ToListAsync();
     }
 }

Upvotes: 0

Bryan Watts
Bryan Watts

Reputation: 45475

Here is my answer to a similar question (28 votes as of right now):

Advantage of creating a generic repository vs. specific repository for each object?

The idea is to genericize the implementation, not the interface. Instead of an outward-facing generic repository interface, create an inward-facing generic repository base class, which you use to easily implement entity-specific interfaces.

Edit: I should point out that generic repositories serve a very different function than specific repositories. Repositories are intended to encapsulate the data access mechanisms behind the entity's queries, including all of the query logic. A generic repository encapsulates the ability to create queries, but it doesn't encapsulate any specific query about an entity.

The point is to not make repository consumers responsible for writing their own queries. A generic repository lives at the same level of abstraction as an ORM; a specific repository lives at a level above that.

Upvotes: 3

ten5peed
ten5peed

Reputation: 15900

Have a look at my answer to the question "Asp.net MVC 2 Entity Framework Generic Repository Method. how to Update a specific Collumn" - it should give you a good idea of what to do.

HTHs, Charles

E.g.

Base model:

public interface IDbTable
{
    int Id { get; set; }
    DateTime DateCreated { get; set; }
    DateTime DateUpdated { get; set; }
}

public class DbTable
{
    public int Id { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateUpdated { get; set; }
}

Your model

public class Category : DbTable
{
    public string Name { get; set; }
}

Your repository

public interface IBaseRepository<T> where T : class, IDbTable
{
    void Add<T>(T entity);
    void Edit<T>(T entity);
    void Remove<T>(T entity);
    T GetById(int id);
}

public class BaseRepository<T> : IBaseRepository<T>
{
    private ISession Session;

    public BaseRepository()
    {
        this.Session = NHibernateSessionFactory.OpenSession();
    }

    public void Add(T entity)
    {
        entity.DateCreated = DateTime.UtcNow;
        entity.DateUpdated = DateTime.UtcNow;

        using(ITransaction transaction = this.Session.BeginTransaction())
        {
            this.Session.Save(entity);
            transaction.Commit();
        }
    }

    public void Edit(T entity)
    {
        entity.DateUpdated = DateTime.UtcNow;

        using(ITransaction transaction = this.Session.BeginTransaction())
        {
            this.Session.Update(entity);
            transaction.Commit();
        }
    }

    public void Remove(T entity)
    {
        using(ITransaction transaction = this.Session.BeginTransaction())
        {
            this.Session.Delete(entity);
            transaction.Commit();
        }
    }

    public T GetById(int id)
    {
        return this.Session.Get<T>(id);
    }
}

Oh, and lets not forget the concrete implementation

public interface ICategoryRepository : IBaseRepository<Category>
{
    Category GetCategoryByName(string categoryName);
}

public CategoryRepository : BaseRepository<Category>
{
    public Category GetCategoryByName(string categoryName)
    {
        //blah
    }
}

Upvotes: 2

rebelliard
rebelliard

Reputation: 9611

From another thread:

public interface IRepository<T> : IQueryable<T>
{
  void Add(T entity);
  T Get(Guid id);
  void Remove(T entity);
}

public class Repository<T> : IQueryable<T>
{
  private readonly ISession session;

  public Repository(ISession session)
  {
    session = session;
  }

  public Type ElementType
  {
    get { return session.Query<T>().ElementType; }
  }

  public Expression Expression
  {
    get { return session.Query<T>().Expression; }
  }

  public IQueryProvider Provider
  {
    get { return session.Query<T>().Provider; } 
  }  

  public void Add(T entity)
  {
    session.Save(entity);
  }

  public T Get(Guid id)
  {
    return session.Get<T>(id);
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return this.GetEnumerator();
  }

  public IEnumerator<T> GetEnumerator()
  {
    return session.Query<T>().GetEnumerator();
  }

  public void Remove(T entity)
  {
    session.Delete(entity);
  }   
}

Upvotes: 6

driis
driis

Reputation: 164341

You should make a generic repository, which you can use in the general case, and if any extra methods is needed for a particular class, add it by using inheritance. Using your example:

public class GenericRepository<TEntity> :IGenericRepository<TEntity> {
    private ISession Session;

    public GenericRepository() {
        this.Session = NHibernateSessionFactory.OpenSession();
    }

    public void Add(TEntity instance) {
        using(ITransaction transaction = this.Session.BeginTransaction()) {
            this.Session.Save(instance);
            transaction.Commit();
        }
    }

    /* other methods */ 
}

public class SpecificRepository : GenericRepository<SpecificEntity>, ISpecificRepository 
{
    public void SpecialQuery()  { /* added method implementation */ }
}

Upvotes: 4

Related Questions