Skip to content

A usage of the repository pattern to abstract data access using LINQ independent of the data source

License

Notifications You must be signed in to change notification settings

devvelopi/Repositori

Repository files navigation

Repositori

A usage of the repository pattern to abstract data access using LINQ independent of the data source

Regarding Repository Pattern

Repositories, being the hotly debated topic that they are, are not for everyone, and is down to personal preference.

The concept behind this library is to be a generic abstraction of data sources in the infrastructure layer.

Tying the power of C# generics with the brilliant tool that is LINQ, it is very possible to create clean, data-source independent code.

Repositories can be used to wrap:

  • Rest endpoints (I.e. CRUD operations)
  • SQL databases (I.e. EntityFramework)
  • NoSQL databases (I.e. DynamoDb)
  • File storage (I.e. S3)
  • and many other data sources

Installation

dotnet add package Repositori.Core

Changelog

See Changelog

Usage

Creating Custom Repositories

Primary use involves creating custom repositories using the IRepository interface. The example below is a simple repository built around a generic list.

public class ListRepository<TEntity, TIdentifier> : IRepository<TEntity, TIdentifier> where TEntity : IIdentifiable<TIdentifier>
{
    private readonly List<TEntity> _entities;
    
    public ListRepository(List<TEntity> entities)
    {
        _entities = entities;
    }

    /// <inheritdoc />
    public IQueryable<TEntity> Query => _entities.AsQueryable();

    /// <inheritdoc />
    public TEntity GetById(TIdentifier id) => _entities.FirstOrDefault(e => e.Id.Equals(id));

    /// <inheritdoc />
    public async Task<TEntity> GetByIdAsync(TIdentifier id) => await Task.Run(() => GetById(id));

    /// <inheritdoc />
    public TEntity Create(TEntity entity)
    {
        _entities.Add(entity);
        return entity;
    }

    /// <inheritdoc />
    public async Task<TEntity> CreateAsync(TEntity entity) => await Task.Run(() => Create(entity));

    /// <inheritdoc />
    public List<TEntity> Create(ICollection<TEntity> entities)
    {
        _entities.AddRange(entities);
        return entities.ToList();
    }

    /// <inheritdoc />
    public async Task<List<TEntity>> CreateAsync(ICollection<TEntity> entities) =>
        await Task.Run(() => Create(entities));

    /// <inheritdoc />
    public TEntity Update(TEntity entity)
    {
        var index = _entities.FindIndex(e => e.Id.Equals(entity.Id));
        if (index <= -1) return default;
        _entities[index] = entity;
        return entity;
    }

    /// <inheritdoc />
    public async Task<TEntity> UpdateAsync(TEntity entity) => await Task.Run(() => Update(entity));

    /// <inheritdoc />
    public List<TEntity> Update(ICollection<TEntity> entities)
    {
        entities.ToList().ForEach(e => Update(e));
        return entities.ToList();
    }

    /// <inheritdoc />
    public async Task<List<TEntity>> UpdateAsync(ICollection<TEntity> entities) =>
        await Task.Run(() => Update(entities));

    /// <inheritdoc />
    public TEntity Delete(TEntity entity)
    {
        _entities.RemoveAll(e => e.Id.Equals(entity.Id));
        return entity;
    }

    /// <inheritdoc />
    public async Task<TEntity> DeleteAsync(TEntity entity) => await Task.Run(() => Delete(entity));

    /// <inheritdoc />
    public List<TEntity> Delete(ICollection<TEntity> entities)
    {
        _entities.RemoveAll(e => entities.Any(en => en.Id.Equals(e.Id)));
        return entities.ToList();
    }

    /// <inheritdoc />
    public async Task<List<TEntity>> DeleteAsync(ICollection<TEntity> entities) => await Task.Run(() => Delete(entities));
    
    /// <inheritdoc />
    public async Task StartTransactionAsync()
    {
    }
    
    /// <inheritdoc />
    public async Task CommitTransactionAsync()
    {
    }

    /// <inheritdoc />
    public async Task RollbackTransactionAsync()
    {
    }
}

Consumption of Custom Repositories

Usage is simple. Even more simple with dependency injection. Using the previously created example:

public class DataModel : IIdentifiable<string>
{
    public string Id { get; set; }
    public string SomeField { get; set; }
}

...

public class Program
{
    public async Task DoThings()
    {
        var repository = new ListRepository<DataModel, string>(new List<DataModel>
            {new DataModel {Id = "1"}, new DataModel {Id = "2"}});
        var data = await repository.GetByIdAsync("1");
        var query = repository.Query.Where(e => e.SomeField.Equals("SomeValue"));
    }
}

Consumption of Repository Subsets

Additionally, the IRepository interface is a combination of several subsets of functionality reflecting CRUD, namely:

  • ICreateRepository
  • IReadRepository
  • IUpdateRepository
  • IDeleteRepository
  • ITransactionalRepository

The reasoning for separation is from a usability standpoint.

Consider the case where you require readonly access to a data source, this is now trivial:

public class SomeService 
{
    private readonly IReadRepository _repository;
    public SomeService(IReadRepository repository)
    {
        _repository = repository;
    }
}

Works well with

Other Repositori Implementations

See other common Repositori implementations:

Custom LINQ Providers

Writing custom LINQ providers is indeed possible if a little difficult.

By writing custom providers, it is possible to abstract ANY data-source, behind the great pre-existing LINQ functionality.

Uniti

A shameless plug for my other project Uniti, an implementation of the Unit of Work pattern which allows transactional functionality across all operations, not just limited to the database.

Such as the pseudo code below.

public class MockRepository<TEntity, TIdentifier> : IRepository<TEntity, TIdentifier> 
{
    protected readonly IUnitOfWorkBuilder _uow;
    
    public MockRepository() 
    {
        _uow = new UnitOfWorkBuilder();
    }

    ...
    /// <inheritdoc />
    public async Task<TEntity> CreateAsync(TEntity entity)
    {
        _uow.Add(
        async () => 
        { 
            //  Create functionality
        },
        async () => 
        {
            // Rollback create functionality
            await DeleteAsync(entity);
        })
        return entity;
    }
    ...
        
    public async Task StartTransactionAsync() => await _uow.StartAsync();

    public async Task CommitTransactionAsync() => await _uow.CommitAsync();
    
    public async Task RollbackTransactionAsync() => await _uow.RollbackAsync();
}

About

A usage of the repository pattern to abstract data access using LINQ independent of the data source

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages