DEV Community

PeterMilovcik
PeterMilovcik

Posted on

Generic Repository Pattern in .NET

This short post (or rather code block) is just a slightly adapted generic design pattern from MSDN without Unit of Work pattern implementation. I've adapted it because of the requirement for asynchronous APIs for a single Context. I post it primarily to me personally for later use - to find it quickly, but if you find it useful too, I'm glad. If you have any suggestions for further improvements, please comment.

Here is the code snippet:

class Repository<TEntity> : IDisposable where TEntity : class
{
    private bool isDisposed;

    private ILogger<Repository<TEntity>> Logger { get; }
    private ApplicationDbContext Context { get; }
    private DbSet<TEntity> DbSet { get; }

    public Repository(
        ILogger<Repository<TEntity>> logger, 
        ApplicationDbContext context)
    {
        Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        Context = context ?? throw new ArgumentNullException(nameof(context));
        DbSet = Context.Set<TEntity>();
    }

    public virtual Task<List<TEntity>> GetAsync(
        Expression<Func<TEntity, bool>>? filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
        string includeProperties = "")
        {
            IQueryable<TEntity> query = DbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split(",", StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToListAsync();
            }
            else
            {
                return query.ToListAsync();
            }
        }

    public virtual ValueTask<TEntity?> GetByIdAsync(object id, CancellationToken cancellationToken = default) => 
        DbSet.FindAsync(id, cancellationToken);

    public virtual ValueTask<EntityEntry<TEntity>> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => 
        DbSet.AddAsync(entity, cancellationToken);

    public virtual async Task DeleteAsync(object id, CancellationToken cancellationToken = default)
    {
        var entityToDelete = await DbSet.FindAsync(id, cancellationToken);
        if (entityToDelete != null) Delete(entityToDelete);
    }

    public virtual void Delete(TEntity entityToDelete)
    {
        if (Context.Entry(entityToDelete).State == EntityState.Detached)
        {
            DbSet.Attach(entityToDelete);
        }
        DbSet.Remove(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        DbSet.Attach(entityToUpdate);
        Context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public virtual async Task SaveChangesAsync(CancellationToken cancellationToken = default) => 
        await Context.SaveChangesAsync();

    protected virtual void Dispose(bool disposing)
    {
        if (!isDisposed)
        {
            if (disposing)
            {
                Context.Dispose();
            }
            isDisposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Yours is a very good generic repository, save for the fact that uses the annoying Entity Framework, which is of course, my very own personal opinion. Personally I dislike it (EF). I'd rather use Dapper. I too create generic repositories, but I do them using Model Features. See if you like it.

If you can extract a model feature out of your models, you can create base code for that feature, regardless of the feature, and most likely regardless of the ORM. Soft deletion, UPSERT, search by name and even apply RLS (Row-Level Security).

Collapse
 
petermilovcik profile image
PeterMilovcik

Thank you very much for your comment! This is very interesting. I do like your Model Features. There are some good aspects to consider.
Just curious, why do you dislike the Entity Framework, or why do you think Dapper is better? I'd really like to read your opinion.

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Basically I find its Unit of Work implementation useless for REST. Here.