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].
Recommended Objects (Optional)
Recommended Properties (Optional)
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:
- Using the ConnectionPersistency.Instance enumeration.
- ICache is enabled.
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.