Link Search Menu Expand Document

Generic Repository


This page contains the reference implementation for a generic repository usable with any entity model. The consolidated output can be found here.

This repository pattern serves as a reusable base repository for any entity model, functioning similarly to DbRepository.

Cache

Create a custom cache class.

public static class MyCustomCache : MemoryCache
{
    ...
}

Create a factory class.

public static class CacheFactory
{
    private static object _syncLock = new object();
    private static ICache _cache = null;
    
    public static ICache CreateCacher()
    {
        if (_cache == null)
        {
            lock (_syncLock)
            {
                if (_cache == null)
                {
                    _cache = new MyCustomCache();
                }
            }
        }
        return _cache;
    }
}

Or, use dependency injection.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // Registration
    services.AddSingleton<ICache, MyCustomCache>();
}

Trace

Create a custom trace class.

public static class MyCustomTrace : ITrace
{
    /* Implement all the methods here */
}

Create a factory class.

public static class TraceFactory
{
    private static object _syncLock = new object();
    private static ITrace _trace = null;
    
    public static ITrace CreateTracer()
    {
        if (_trace == null)
        {
            lock (_syncLock)
            {
                if (_trace == null)
                {
                    _trace = new MyCustomTrace();
                }
            }
        }
        return _trace;
    }
}

Or, use dependency injection.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // Registration
    services.AddSingleton<ITrace, MyCustomTrace>();
}

Settings

Inject the settings object via the repository constructor. See the Microsoft documentation for details.

public class AppSetting
{
    public string ConnectionString { get; set; }
    public int CommandTimeout { get; set; }
    public int CacheItemExpiration { get; set; }
}

Interface

Create an interface covering all required methods.

public interface IRepositoryBase<TDbConnection>
    where TDbConnection : DbConnection
{
    /*** Helper ***/

    TDbConnection GetConnection();

    /*** Non-Async ***/

    IEnumerable<TEntity> GetAll<TEntity>(string cacheKey = null);

    TEntity Get<TEntity>(object id);

    int Delete<TEntity>(object id);

    int Merge<TEntity>(TEntity entity);

    object Save<TEntity>(TEntity entity);

    object Update<TEntity>(TEntity entity);

    /*** Async ***/

    Task<IEnumerable<TEntity>> GetAllAsync<TEntity>(string cacheKey = null);

    Task<TEntity> GetAsync<TEntity>(int id);

    Task<int> DeleteAsync<TEntity>(int id);

    Task<objec> MergeAsync<TEntity>(TEntity entity);
    
    Task<int> SaveAsync<TEntity>(TEntity entity);

    Task<int> UpdateAsync<TEntity>(TEntity entity);
}

Repository (Base)

The following implementation satisfies the IRepositoryBase interface defined above.

public class RepositoryBase<TDbConnection> : IRepositoryBase<TDbConnection>
    where TDbConnection : DbConnection
{
    private IOptions<AppSettings> _settings;

    public RepositoryBase(IOptions<AppSetting> settings,
        ICache cache,
        ITrace trace)
    {
        _settings = settings;
        Cache = cache;
        Trace = trace;
    }

    /*** Properties ***/

    public ITrace Trace { get; }

    public ICache Cache { get; }

    ...
}

Interface Methods

Implement all methods defined in the base interface.

Create Connection

public TDbConnection CreateConnection()
{
    var connection = Activator.CreateInstance<TDbConnection>();
    connection.ConnectionString = _settings.Value.ConnectionString;
    return connection;
}

Get

public IEnumerable<TEntity> GetAll<TEntity>(string cacheKey = null)
{
    using (var connection = CreateConnection())
    {
        return connection.QueryAll<TEntity>(cacheKey: cacheKey,
            commandTimeout: _settings.CommandTimeout,
            cache: Cache,
            cacheItemExpiration: _settings.CacheItemExpiration,
            trace: Trace);
    }
}

public TEntity Get<TEntity>(object id)
{
    using (var connection = CreateConnection())
    {
        return connection.Query<TEntity>(id,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Delete

public int Delete<TEntity>(object id)
{
    using (var connection = CreateConnection())
    {
        return connection.Delete<TEntity>(id,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Merge

public object Merge<TEntity>(TEntity entity)
{
    using (var connection = CreateConnection())
    {
        return connection.Merge<TEntity>(entity,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Save

public object Save<TEntity>(TEntity entity)
{
    using (var connection = CreateConnection())
    {
        return connection.Insert<TEntity>(entity,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Update

public int Update<TEntity>(TEntity entity)
{
    using (var connection = CreateConnection())
    {
        return Update<TEntity>(entity,
            trace: Trace);
    }
}

Async Methods

Each synchronous method must have a corresponding asynchronous counterpart with the Async suffix, delegating to the library’s async operations.

Get

public async Task<IEnumerable<TEntity>> GetAllAsync<TEntity>(string cacheKey = null)
{
    using (var connection = CreateConnection())
    {
        return await connection.QueryAllAsync<TEntity>(cacheKey: cacheKey,
            commandTimeout: _settings.CommandTimeout,
            cache: Cache,
            cacheItemExpiration: _settings.CacheItemExpiration,
            trace: Trace);
    }
}

public async Task<TEntity> GetAsync<TEntity>(object id)
{
    using (var connection = CreateConnection())
    {
        return await connection.QueryAsync<TEntity>(id,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Delete

public async Task<int> DeleteAsync<TEntity>(object id)
{
    using (var connection = CreateConnection())
    {
        return await connection.DeleteAsync<TEntity>(id,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Merge

public async Task<object> MergeAsync<TEntity>(TEntity entity)
{
    using (var connection = CreateConnection())
    {
        return await connection.MergeAsync<TEntity>(entity,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Save

public async Task<object> SaveAsync<TEntity>(TEntity entity)
{
    using (var connection = CreateConnection())
    {
        return await connection.InsertAsync<TEntity>(entity,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Update

public async Task<int> UpdateAsync<TEntity>(TEntity entity)
{
    using (var connection = CreateConnection())
    {
        return await connection.UpdateAsync<TEntity>(entity,
            commandTimeout: _settings.CommandTimeout,
            trace: Trace);
    }
}

Using the RepositoryBase

Create a class that inherits the base repository.

Using factory classes:

public class NorthwindRepository : RepositoryBase<SqlConnection>
{
    public NorthwindRepository(IOptions<AppSetting> settings)
        : base(settings)
    { }
}

Using dependency injection:

public class NorthwindRepository : RepositoryBase<SqlConnection>
{
    public NorthwindRepository(IOptions<AppSetting> settings,
        ICache cache,
        ITrace trace)
        : base(settings,
            cache,
            trace)
    { }
}

Derived Repository Methods

Recommended implementation for retrieving a single record:

public Customer GetCustomer(int id)
{
    return base.Get<Customer>(customer);
}

public Order GetOrder(int id)
{
    return base.Get<Order>(order);
}

public Product GetProduct(int id)
{
    return base.Get<Product>(product);
}

Recommended implementation for saving a record:

public int SaveCustomer(Customer customer)
{
    return base.Save<Customer, int>(customer);
}

public int SaveOrder(Order order)
{
    return base.Save<Order, int>(order);
}

public int SaveProduct(Product product)
{
    return base.Save<Product, int>(product);
}

Do the same calls for the other methods. Also, please take note to always implement the corresponding asynchronous methods.

Interface for Derived Repository

Implement the interface for the derived repository.

public interface INorthwindRepository
{
    /*** Non-Async ***/

    // Get

    Customer GetCustomer(int id);

    Order GetOrder(int id);

    Product GetProduct(int id);

    // Delete

    ...

    // Merge

    ...

    // Save

    int SaveCustomer(Customer customer);

    int SaveOrder(Order order);

    int SaveProduct(Product product);

    void SaveCustomerOrder(int customerId,
        int productId);
        
    // Update

    ...

    /*** Async **/

    ...
}

Derived Interface Implementation

Create a class that inherits the repository and implements the interface.

For the factory classes.

public class NorthwindRepository : RepositoryBase<SqlConnection>, INorthwindRepository
{
    public NorthwindRepository(IOptions<AppSetting> settings)
        : base(settings)
    { }
}

For the dependency-injected classes.

public class NorthwindRepository : RepositoryBase<SqlConnection>, INorthwindRepository
{
    public NorthwindRepository(IOptions<AppSetting> settings,
        ICache cache,
        ITrace trace)
        : base(settings,
            cache,
            trace)
    { }
}

Service Configuration and Registration

Register it as singleton if you…

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // Registration
    services.AddSingleton<INorthwindRepository, NorthwindRepository>();
}

Otherwise, register it as transient.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // Registration
    services.AddTransient<INorthwindRepository, NorthwindRepository>();
}

Key Take-aways

  • The dependency injection must be happened only in the derived repositories.
  • The async methods must be provided in all methods.
  • The repository must be short and precise on its purpose.