In this part, let’s implement the IUserRepository
and IUnitOfWork
interfaces in UserManagement.Persistence and define strongly typed constants values class loading from appsettings.json. This would help us to understand how these patterns work to perform database operations.
Let’s Start
Add the three folders Constant, Repositories, and UnitOfWork in UserManagement.Persistence by right-clicking on the project name and selecting Add -> New Folder.
UserRepository Implementation
Before implementing the IUserRepository
implementation, let’s create the base repository class which will contain the common code that all repository classes can extend e.g. database connection and transaction initialization. Create a class Repository
in the Repositories folder and replace the content with the following code:
namespace UserManagement.Persistence.Repositories
{
using System.Data;
public class Repository
{
protected IDbConnection DbConnection { get; }
protected IDbTransaction DbTransaction { get; }
public Repository(IDbConnection dbConnection, IDbTransaction dbTransaction)
{
DbTransaction = dbTransaction;
DbConnection = dbConnection;
}
}
}
We will pass the DbConnection
and DbTransaction
objects from the unit of work class and will use it in a UserRepository
class after initializing it in base Repository
class. Create a new class UserRepository
in the Repositories folder and add the following code to it:
namespace UserManagement.Persistence.Repositories
{
using Dapper.Contrib.Extensions;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using UserManagement.Domain.Entities;
using UserManagement.Domain.Repositories;
public class UserRepository: Repository, IUserRepository
{
public UserRepository(IDbConnection dbConnection, IDbTransaction dbtransaction)
: base(dbConnection, dbtransaction)
{
}
public Task<int> AddUser(User user)
{
user.DateAdded = DateTime.Now;
user.DateUpdated = null;
return DbConnection.InsertAsync(user, DbTransaction);
}
public Task<bool> DeleteUser(int userId)
{
return DbConnection.DeleteAsync(new User { UserID = userId }, DbTransaction);
}
public Task<IEnumerable<User>> GetAllUsers()
{
return DbConnection.GetAllAsync<User>();
}
public Task<User> GetUser(long userId)
{
return DbConnection.GetAsync<User>(userId);
}
public Task<bool> UpdateUser(User user)
{
user.DateUpdated = DateTime.Now;
return DbConnection.UpdateAsync(user, DbTransaction);
}
public Task<bool> DeleteAllUser()
{
return DbConnection.DeleteAllAsync<User>();
}
}
}
You can see the that UserRepositoy
is extending the Repository
base class, implementing the IUserRepository
interface and passing the DbConnection
and DbTransaction
variables in UserRepository
class constructor to Repository
class constructor where we are initialization it as you can see in the Repository
class.
You would receive the error if you will try to build the project because the IUserRepository
interface is defined in UserManagement.Domain Project so we need to add the Domain reference into UserManagement.Persistence project. Right-click on Dependencies in UserManagement.Persistence folder and select Add Project Reference. Check the UserManagement.Domain project checkbox and click OK button.
We are using Dapper.Contrib extension for Dapper that is helping us to create the CRUD operation and mapping the result to the required model. For example, you can see the GetAllUsers()
function, we are getting the list of User entity class object. In AddUser(User user)
function, we are passing the User entity object and inserting it into the database (not really, it is being saved in the in-memory database unless we commit the transaction because each operation is treated as a single transaction), the Dapper.Contrib is making it possible to automatically mapping the objects and executing the insert query in the background. The same goes for the rest of the functions. Remember, you are more than welcome NOT to use Dapper.Contrib package and execute the queries and map the result to model/entity in the traditional way.
Unit Of Work Implementation
Create a new class UnitOfWork
in UnitOfWork folder and replace its content with following code:
namespace UserManagement.Persistence.UnitOfWork
{
using System.Data;
using UserManagement.Domain.Repositories;
using UserManagement.Domain.UnitOfWork;
using UserManagement.Persistence.Repositories;
public class UnitOfWork : IUnitOfWork
{
private IDbConnection dbConnection;
private IDbTransaction transaction;
public UnitOfWork(IDbConnection dbConnection)
{
this.dbConnection = dbConnection;
this.ManageConnection();
}
public IUserRepository Users => new UserRepository(this.dbConnection, this.transaction);
public void StartTransaction()
{
if (this.transaction == null)
{
this.transaction = this.dbConnection.BeginTransaction();
}
}
public void Commit()
{
try
{
this.transaction.Commit();
}
catch
{
this.transaction.Rollback();
}
}
private void ManageConnection()
{
if (this.dbConnection.State == ConnectionState.Closed)
{
this.dbConnection.Open();
}
}
}
}
Let’s try to understand the UnitOfWork class, there are two private variables dbConnection
and transaction
where dbConnection
is being initialized in UnitOfWork
constructor and transaction
is initialized in public method StartTransaction()
that we will call explicitly in UserMangement.Application class to treat all repository function(s) as a single transaction. You can see that the Users
variable of IUserRepository
type is being initialized and we are passing dbConnection
and transaction
as input parameters that go to the base Repository
class where we are using them in UserRepository class (and can use it in any repository class).
There is one more public method Commit()
that is committing o rollbacking the transaction. We will also call this method explicitly in UserManagement.Application layer when we would want to commit the changes to the database.
The ManagementConnection()
method calling from constructor is just opening the database connection if it is already closed.
Now let’s go back to the UnitOfWork
constructor, you can see that we are getting dbConnection
variable of IDBConnection type and assigning it to a private variable but the question is where are we initializing the UnitOfWork
class and passing the IDBConnection
type variable?
The answer is built-in dependency injection in ASP.NET Core. So, let’s create a DependencyInjection.IServiceCollection
extension method and add the Persistence layer dependencies to it. Later, we will call this extension method in UserManagement.API project’s StartUp.ConfigureService()
method/event.
Before adding the dependency Injection class, first, let’s add the third party required packages. Remember, there would be still two dependencies from UserManagement.Application and Persistence that will show errors but we will add them in upcoming steps. Right-click on the Dependencies folder in UserManagement.Persistence layer and search and install the following packages:
- Dapper.Contrib
- Microsoft.Extensions.Configuration.Abstractions
- Microsoft.Extensions.DependencyInjection.Abstractions
Right-click on UserManagement.Persistence project and select Add -> Class... Enter the class name DependencyInjection
and replace its content with the following code:
namespace UserManagement.Persistence
{
using Microsoft.Extensions.DependencyInjection;
using System.Data;
using System.Data.SqlClient;
using UserManagement.Application.Common.Interfaces;
using UserManagement.Domain.UnitOfWork;
using UserManagement.Persistence.Constant;
public static class DependencyInjection
{
public static IServiceCollection AddPersistance(this IServiceCollection services)
{
services.AddSingleton<IConfigConstants, ConfigConstants>();
services.AddSingleton<IDbConnection>(conn => new SqlConnection(conn.GetService<IConfigConstants>().FullStackConnection));
services.AddTransient<IUnitOfWork>(uof => new UnitOfWork.UnitOfWork(uof.GetService<IDbConnection>()));
return services;
}
}
}
There are three dependencies we are specifying here, the first one is ConfigConstants
class that we are going to create next, this class will contain all the values from appsetting.json
but strongly typed. The IConfigConstants
interface will be in UserManagement.Application project so don’t worry if the project is not compile-able yet.
The next dependency is database connection where we are getting the connecting string FullStackConnection
from ConfigConstant
class.
The last dependency is the answer to the question we had where UnitOfWork
is being initialized and getting the DbConnection
object as a parameter. It’s not a typical case but you can see that the dependencies are dependant on each other.
Implement ConfigConstants Class
Now that we understood the purpose of ConfigConstants
class, let’s create the concrete class in UserManagement.Persistence and interface in UserManagement.Application projects. Right-click on the Constant folder and create a class ConfigConstants
, replace the content with the following code:
namespace UserManagement.Persistence.Constant
{
using Microsoft.Extensions.Configuration;
using UserManagement.Application.Common.Interfaces;
public class ConfigConstants : IConfigConstants
{
public IConfiguration Configuration { get; }
public ConfigConstants(IConfiguration configuration)
{
this.Configuration = configuration;
}
public string FullStackConnection => this.Configuration.GetConnectionString("FullStackConnection");
public string TestFullStackConnection => this.Configuration.GetConnectionString("TestFullStackConnection");
public int LongRunningProcessMilliseconds => int.Parse(this.Configuration["AppSettings:LongRunningProcessMilliseconds"]);
public string MSG_USER_NULLUSERID => this.Configuration["AppSettings:MSG_USER_NULLUSERID"];
public string MSG_USER_NULLFIRSTNAME => this.Configuration["AppSettings:MSG_USER_NULLFIRSTNAME"];
public string MSG_USER_NULLLASTNAME => this.Configuration["AppSettings:MSG_USER_NULLLASTNAME"];
public string MSG_USER_NULLDOB => this.Configuration["AppSettings:MSG_USER_NULLDOB"];
public string MSG_USER_NULLGENDER => this.Configuration["AppSettings:MSG_USER_NULLGENDER"];
public string MSG_USER_GENDER_LEN => this.Configuration["AppSettings:MSG_USER_GENDER_LEN"];
public string MSG_USER_NULLEMAILADDR => this.Configuration["AppSettings:MSG_USER_NULLEMAILADDR"];
public string MSG_USER_NULLPHNUM => this.Configuration["AppSettings:MSG_USER_NULLPHNUM"];
public string MSG_USER_NULLCITY => this.Configuration["AppSettings:MSG_USER_NULLCITY"];
public string MSG_USER_NULLSTATE => this.Configuration["AppSettings:MSG_USER_NULLSTATE"];
public string MSG_USER_NULLCOUNTRY => this.Configuration["AppSettings:MSG_USER_NULLCOUNTRY"];
}
}
In the above-given class, you can understand now how we are creating strongly typed config values. Since this is part of the values saved in the appsetting.json file, it looks like a good candidate to keep the concrete class in Application.Persistence project but config values would be used in UserManagement.Application project so we will be keeping the interface there to use it easily.
Let’s create the IConfigConstants class so that our solution would be compile-able. In UserManagement.Application project, create new folder Common, and in the Common folder, create one child folder Interfaces. In the Interface folder, create an interface IConfigConstants
and replace the code with following:
namespace UserManagement.Application.Common.Interfaces
{
public interface IConfigConstants
{
string FullStackConnection { get; }
string TestFullStackConnection { get; }
int LongRunningProcessMilliseconds { get; }
string MSG_USER_NULLUSERID { get; }
string MSG_USER_NULLFIRSTNAME { get; }
string MSG_USER_NULLLASTNAME { get; }
string MSG_USER_NULLDOB { get; }
string MSG_USER_NULLGENDER { get; }
string MSG_USER_GENDER_LEN { get; }
string MSG_USER_NULLEMAILADDR { get; }
string MSG_USER_NULLPHNUM { get; }
string MSG_USER_NULLCITY { get; }
string MSG_USER_NULLSTATE { get; }
string MSG_USER_NULLCOUNTRY { get; }
}
}
Add the UserManagement.Application project dependency in UserManagement.Persistence project by right-clicking on Dependencies and select the Add Project Dependencies, check the UserManagement.Application project and click OK button.
That’s it for Domain and Persistence layer, in the next part we will implment the Application and APIs projects.