BaseRepository
This page contains the reference implementation when implementing a repository that inherits the BaseRepository class. The consolidated output of this page can be found here.
The BaseRepository class is only being used if you wished to implement an entity-based repository. Imagine that you are only working with
[dbo].[Customer]
table.
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, if you wish to dependency inject.
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, if you wish to dependency inject.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Registration
services.AddSingleton<ITrace, MyCustomTrace>();
}
Settings
The settings object must be injected within the constructor of the repository. Please refer to Microsoft documentation.
public class AppSetting
{
public string ConnectionString { get; set; }
public int CommandTimeout { get; set; }
public int CacheItemExpiration { get; set; }
}
Repository
Simply inherit the BaseRepository class and pass the generic types for the entity-model and connection object.
Below is the sample repository implementation.
For the 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)
{ }
...
}
For the dependency-injected classes.
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
Below is the recommended way when exposing a method that returns all the records.
public IEnumerable<Customer> GetAll(string cacheKey = null,
IDbTransaction transaction = null)
{
return QueryAll(cacheKey: cacheKey,
transaction: transaction);
}
Below is the recommended way when exposing 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();
}
Below is the recommended way when exposing a method that deletes a record.
public int Delete(int id,
IDbTransaction transaction = null)
{
return base.Delete(id,
transaction: transaction);
}
Below is the recommended way when exposing a method that pushes 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
Ensure that all the synchronous methods you had created has the corresponding asynchronous methods suffixed by Async
keyword. Within these methods, ensure that you are calling the corresponding asynchronous operations of the library.
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 that contains all the necessary methods (both sync and async). The name must be identitical on the purpose of the repository.
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 it as singleton if you…
- Are using the ConnectionPersistency.Instance enumeration.
- Enabled the ICache object.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Registration
services.AddSingleton<ICustomerRepository, CustomerRepository>();
}
Otherwise, register it as transient.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Registration
services.AddTransient<ICustomerRepository, CustomerRepository>();
}
Key Take-aways
- The transaction argument is needed in every method in order for you to enable the Unit of Work (UOW).
- The cache key argument is needed in the case you need to cache the result.
- The interface is needed for dependency injection.
- The singleton registration is needed for caching and connection persistency.
- The repository must be short and precise on its purpose.