In this part, we will create the User entity, Repository, and Unit of Work classes. We will understand how Dapper and Dapp.Contrib work to create the user entity and perform CRUD operations. We will also learn about the Repository, Unit of Work patterns, and their benefits along with the implementation.
Let’s Start
First, we will create the User entity, repository, and unit of work interfaces in UserManagement.Domain project and then implement the repository and unit of work in UserManagement.Persistence project. Keeping the project dependencies according to Jason Taylor’s architecture, the Domain project would be an independent project whereas the Persistence project would have Domain and Application projects as dependencies. This would create a very loosely coupled architecture that would be great for unit and integration testings that we will see in upcoming parts.
Create a Database
Open the Microsoft SQL Server Management Studio, New Query and run the first query:
CREATE DATABASE FullstackHub
Once the database is created, run the second query:
USE [FullstackHub]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[User](
[UserID] [bigint] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](250) NOT NULL,
[LastName] [varchar](250) NOT NULL,
[DOB] [datetime] NOT NULL,
[Gender] [char](1) NOT NULL,
[EmailAddress] [varchar](300) NOT NULL,
[PhoneNumber] [varchar](50) NULL,
[City] [varchar](100) NOT NULL,
[State] [varchar](100) NOT NULL,
[Zip] [numeric](10, 0) NOT NULL,
[Country] [varchar](250) NOT NULL,
[DateAdded] [datetime] NOT NULL,
[DateUpdated] [datetime] NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[UserID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_DateUpdated] DEFAULT (NULL) FOR [DateUpdated]
GO
USE [master]
GO
ALTER DATABASE [FullstackHub] SET READ_WRITE
GO
Add User Entity, Repository and Unit of Work Interfaces
We are going to work at UserManagement.Domain project, first we need to add one dependency Dapper.Contrib. If you are new to dapper, there is a very brief example available here. Dapper is a micro ORM that provides pretty much the same functionalities as Entity Framework Core but it is fast, flexible, and easy to use. The Dapper.Contrib is a Dapper extension that provides CRUD functions without writing queries. Since I am lazy and came from an Entity framework background, it looks great for simple CRUD operations. You can also write insert, delete, get, and update queries like here and map the result to model class if you have complex queries or stored procedures.
In UserManagement.Domain project, write click on Dependencies folder, select Manage NuGet Packages…, search for dapper.contrib, and install it. The Dapper.Contrib comes with all required dependencies including the Dapper package.
Next, create a new folder in UserManagement.Domain by writing clicking on the project name and selecting option Add -> New Folder. Enter the folder name Entities. Create a new class User in the Entities folder. The User class will have all properties exactly matching the User table’s columns in the database. Since we are using the Dapper.Contrib, we will also add the User class header as [Table(“[User]”)] and UserID property header as [Key] to identify it as a primary key. Replace the newly created User class content with the following code:
namespace UserManagement.Domain.Entities
{
using Dapper.Contrib.Extensions;
using System;
[Table("[User]")]
public class User
{
[Key]
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
public string Gender { get; set; }
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
public DateTime DateAdded { get; set; }
public DateTime? DateUpdated { get; set; }
}
}
Create a new folder Repositories in the project and add a new interfaceIUserRepositor
. Replace its content with the following code:
namespace UserManagement.Domain.Repositories
{
using System.Collections.Generic;
using System.Threading.Tasks;
using UserManagement.Domain.Entities;
public interface IUserRepository
{
Task<int> AddUser(User user);
Task<bool> UpdateUser(User user);
Task<bool> DeleteUser(int userId);
Task<IEnumerable<User>> GetAllUsers();
Task<User> GetUser(long userId);
Task<bool> DeleteAllUser();
}
}
The IUserRepository
has all functions that we will be needing in upcoming parts to create a front end. Feel free to add more functions for learning.
The last interface required in Domain project is a IUnitOfWork
containing User Repository reference, StartTransaction()
and Commit()
functions definition. Create a new folder in a project UnitOfWork, add a new interface, and replace the content with the following:
namespace UserManagement.Domain.UnitOfWork
{
using UserManagement.Domain.Repositories;
public interface IUnitOfWork
{
IUserRepository Users { get; }
void StartTransaction();
void Commit();
}
}
Why Repository and Unit of Work Design Pattern?
There are plenty of tutorials available on Repository and Unit of Work design patterns but let’s learn why we are using them here. There are a couple of properties of Repository patterns e.g.:
- A repository should act like an in-memory database that means we shouldn’t do actual database add, update or delete implementation in the repository class.
- Repository act as a medium between the domain layer and database layer so they are independent of the underlying framework.
- Repository helps to avoid database logic duplication, you create all DB operation as functions in the repository and call them wherever required.
- Since we are creating a repository interface IUserRepository, it would be easy for us to mock the repository dependency for UserManagement.Application unit testing.
So we have a repository, why the hell we need a unit of work, the answer is related to the in-memory definition of the repository pattern. The unit of work class will commit the add, update or delete records to the database. Following are the main properties of a unit of work class:
- The unit of work class will have a reference to all repository classes, so the UserManagement.Application project will only talk to the unit of work class to perform repository function. We will see this in action in upcoming parts.
- The unit of work has
StartTransaction()
to treat any database operation e.g. add, update and delete as a single transaction to provide database atomicity You will see this in action when we will call any function from the user repository class in UserManagement.Application layer. So basically, we will first callStartTransaction()
function, then our required user repository function(s) and finally we will callCommit()
function to actually commit our changes to the database. If you are from an Entity Framework background, this is a kind ofSaveChanges()
function.
Let’s implement the IUserRepository and IUnitOfWork interfaces in next part.