DEV Community

PeterMilovcik
PeterMilovcik

Posted on

3

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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

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.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more