Link Search Menu Expand Document

BaseRepository


This page contains the reference implementation for a repository that inherits the BaseRepository class. The consolidated output can be found here.

The BaseRepository class is intended for entity-based repositories. Use it when working with a single table, such as [dbo].[Customer].

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; }
}

Repository

Inherit the BaseRepository class and provide the entity model and connection object as generic type arguments.

Using factory classes:

public class CustomerRepository : BaseRepository<Customer, SqlConnection>
{
    public CustomerRepository(IOptions<AppSetting> settings)
        : base(settings.Value.ConnectionString,
            commandTimeout: settings.CommandTimeout,
            connectionPersistency: ConnectionPersistency.PerCall,
            cache: CacheFactory.CreateCacher(),
            cacheItemExpiration: settings.CacheItemExpiration,
            trace: TraceFactory.CreateTracer(),
            statementBuilder: null)
    { }

    ...
}

Using dependency injection:

public class CustomerRepository : BaseRepository<Customer, SqlConnection>
{
    public CustomerRepository(IOptions<AppSetting> settings,
        ICache cache,
        ITrace trace)
        : base(settings.Value.ConnectionString,
            commandTimeout: settings.CommandTimeout,
            connectionPersistency: ConnectionPersistency.PerCall,
            cache: cache,
            cacheItemExpiration: settings.CacheItemExpiration,
            trace: trace,
            statementBuilder: null)
    { }

    ...
}

Methods

Recommended implementation for a method that returns all records:

public IEnumerable<Customer> GetAll(string cacheKey = null,
    IDbTransaction transaction = null)
{
    return QueryAll(cacheKey: cacheKey,
        transaction: transaction);
}

Recommended implementation for a method that returns a single record:

public Customer Get(int id,
    string cacheKey = null,
    IDbTransaction transaction = null)
{
    return Query(id,
        cacheKey: cacheKey,
        transaction: transaction).FirstOrDefault();
}

public Customer GetByName(string name,
    string cacheKey = null,
    IDbTransaction transaction = null)
{
    return Query(p => p.Name == name,
        cacheKey: cacheKey,
        transaction: transaction).FirstOrDefault();
}

Recommended implementation for a method that deletes a record:

public int Delete(int id,
    IDbTransaction transaction = null)
{
    return base.Delete(id,
        transaction: transaction);
}

Recommended implementation for methods that write a record:

public int Merge(Customer customer,
    IDbTransaction transaction = null)
{
    return base.Merge<int>(customer,
        transaction: transaction);
}

public int Save(Customer customer,
    IDbTransaction transaction = null)
{
    return Insert<int>(customer,
        transaction: transaction);
}

public int Update(Customer customer,
    IDbTransaction transaction = null)
{
    return base.Update(customer,
        transaction: transaction);
}

Async Methods

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

public async Task<IEnumerable<Customer>> GetAllAsync(string cacheKey = null,
    IDbTransaction transaction = null)
{
    return await QueryAllAsync(cacheKey: cacheKey,
        transaction: transaction);
}

public async Task<Customer> GetAsync(int id,
    string cacheKey = null,
    IDbTransaction transaction = null)
{
    return (await QueryAsync(id,
        cacheKey: cacheKey,
        transaction: transaction)).FirstOrDefault();
}

public async Task<Customer> GetByNameAsync(string name,
    string cacheKey = null,
    IDbTransaction transaction = null)
{
    return (await QueryAsync(p => p.Name == name,
        cacheKey: cacheKey,
        transaction: transaction)).FirstOrDefault();
}

public async Task<int> DeleteAsync(int id,
    IDbTransaction transaction = null)
{
    return await base.DeleteAsync(id,
        transaction: transaction);
}

public async Task<int> MergeAsync(Customer customer,
    IDbTransaction transaction = null)
{
    return await base.MergeAsync<int>(customer,
        transaction: transaction);
}

public async Task<int> SaveAsync(Customer customer,
    IDbTransaction transaction = null)
{
    return await InsertAsync<int>(customer,
        transaction: transaction);
}

public async Task<int> UpdateAsync(Customer customer,
    IDbTransaction transaction = null)
{
    return await base.UpdateAsync(customer,
        transaction: transaction);
}

Dependency Injection

Create an interface covering all synchronous and asynchronous methods. Name the interface to reflect the repository’s purpose.

public interface ICustomerRepository
{
    // Non-Async

    IEnumerable<Customer> GetAll(string cacheKey = null,
        IDbTransaction transaction = null);

    Customer Get(int id,
        string cacheKey = null,
        IDbTransaction transaction = null);

    Customer GetByName(string name,
        string cacheKey = null,
        IDbTransaction transaction = null);

    int Delete(int id,
        IDbTransaction transaction = null);

    int Merge(Customer customer,
        IDbTransaction transaction = null);

    int Save(Customer customer,
        IDbTransaction transaction = null);

    int Update(Customer customer,
        IDbTransaction transaction = null);

    // Async

    Task<IEnumerable<Customer>> GetAllAsync(string cacheKey = null,
        IDbTransaction transaction = null);

    Task<Customer> GetAsync(int id,
        string cacheKey = null,
        IDbTransaction transaction = null);

    Task<Customer> GetByNameAsync(string name,
        string cacheKey = null,
        IDbTransaction transaction = null);

    Task<int> DeleteAsync(int id,
        IDbTransaction transaction = null);

    Task<int> Merge(Customer customer,
        IDbTransaction transaction = null);

    Task<int> SaveAsync(Customer customer,
        IDbTransaction transaction = null);

    Task<int> UpdateAsync(Customer customer,
        IDbTransaction transaction = null);
}

Then implement it on the repository.

public class CustomerRepository : BaseRepository<Customer, SqlConnection>, ICustomerRepository
{
    ...
}

Service Configuration and Registration

Register as singleton when:

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

    // Registration
    services.AddSingleton<ICustomerRepository, CustomerRepository>();
}

Otherwise, register as transient.

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

    // Registration
    services.AddTransient<ICustomerRepository, CustomerRepository>();
}

Key Take-aways

  • Pass the transaction argument in every method to support the Unit of Work (UOW) pattern.
  • Pass the cache key argument to enable result caching.
  • Define an interface to support dependency injection.
  • Register as singleton when caching or connection persistency is required.
  • Keep each repository focused on a single, well-defined purpose.