Generic Repository Pattern in .NET
Tags: dotnet, csharp, programming, tutorial
Published: 2022-11-08 08:44:25

This short post (or rather code block) is just a slightly adapted generic design pattern from [MSDN](https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application) 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:

```csharp
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);
    }
}
```