Introduction
Most of the times when we have a Web API project, we will also have a database. If you don’t have the knowledge necessary to use SQL language to create and maintain a database, there are solution which are using code first approach. Now is easier than ever to maintain a database, but with each update our database is getting more tables, more columns and is harder and harder to maintain it. In this article, I want to provide one of many solutions out there which can make the “mess much cleaner”.
Short Definitions
Entity Framework
Entity Framework is an open-source ORM-Framework for .NET applications supported by Microsoft. Allows developers to work with data using objects of domain specific classes without focusing on the underlying database tables and columns where this data is stored. With the Entity Framework, developers can work at a higher level of abstraction when they deal with data, and can create and maintain data-oriented applications with less code compared with traditional applications
For more information regarding Entity Framework I strongly recommend this website https://www.entityframeworktutorial.net/, here you can learn a lot about this framework and start build your own app with Entity Framework.
Unit of Work
Unit of Work is like a business transaction. This pattern will merge all CRUD transactions of Repositories into a single transaction. All changes will be committed only once. The main advantage is that application layer will need to know only one class (Unit of Work) to access each repository.
Repository
Repositories are classes which implements data access logic. A repository represents a data entity, common CRUD operations and other special cases. The application layers consumes the APIs provided by the repository and does not need to care about how is implemented.
Generic Repository
In each repository we will have to write the same CRUD operations, delete entity by id, get entity by id, and so on. In order to avoid copy, paste and try to follow DRY principle (Don’t Repeat Yourself) comes in handy Generic Repository pattern, in this class will be implemented all CRUD operations which will be used by all repository’s.
Implementation
Note
For this example, we will use Visual Studio 2019 edition.
Creating Projects
To separate the application layer from the database technology, in this case Entity Framework, we need to create 3 projects:
- Web API- will store the controllers and the logic (application layer)
- Domain-will store the models’ classes and interfaces know by Web API project
- DataAccessEF- will contain the layer where all the content related with Entity Frameworks is implemented
For Web API project we will select ASP.NET Core Web API
For Domain and DataAccessEF project we will select Class library for NET Core
After all project were created, we will have the following projects layout
Creating Models
Let's start creating the model classes. For this example, lest create 3 classes, Person, Address, and Email.
One person will have one address and multiple email addresses.
public class Person {
public int PersonId {
get;
set;
}
public string Name {
get;
set;
}
public int Age {
get;
set;
}
public Address Address {
get;
set;
}
public List < Email > Emails {
get;
set;
}
}
public class Address {
public int AddressId {
get;
set;
}
public string StreetAdress {
get;
set;
}
public string City {
get;
set;
}
public string State {
get;
set;
}
public string ZipCode {
get;
set;
}
}
public class Email {
public int EmailId {
get;
set;
}
public string EmailAdress {
get;
set;
}
}
Creating Context
Let's move to DataAccessEf project and create Context class.
The context class is the link between Database and our C# code. All the properties within this class will represent a table in our database
First, add a reference to the Domain project and include Microsoft.EntityFrameworkCore.Sql to our project by using NuGet manager
And now the Context class. Don’t forget to add a reference to Microsoft.EntityFrameworkCore to use EF specific types.
public class PeopleContext: DbContext {
public PeopleContext(DbContextOptions options): base(options) {}
public DbSet < Person > Person {
get;
set;
}
public DbSet < Address > Address {
get;
set;
}
public DbSet < Email > Email {
get;
set;
}
}
As you can see, the PeopleContext class have 3 properties of each type from the Domain project.
Link DataAccesEF and Domain projects to WebAPI project
Now the context class needs to be linked to the main project WebAPI.
First we need to add a reference in WebAPI project to Domain and DataAccessEF projects. After the references have been added, go to NuGet Manager and install Microsoft.EntityFrameworkCore.Tools in WebAPI.
If everything was ok until here, you should have something like this
Open StartUp.cs class and in ConfigureServices () method and let’s write the link to Context class and enable the connection string to our database by using the following code
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext < PeopleContext > (options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
Now open appsettings.json file and write the connection string for the database. After the edit you should have something similar with this.
Migrations and update database
To create a database using Entity Framework is easy, we need to add a migration and send the create command to our local server. Let's create the database.
Open Package Manager Console, select DataAccesEF project as default project and type add-migration CreateInitialDB and press enter.
If everything was ok, you should see a Build succeeded message. Now let’s create the database by using another command, update-database and press enter.
Now we have a database created.
Creating Interfaces
In order to have a decupled environment we need to use as much as possible interfaces and these interfaces needs to be declared on Domain project, because we can replace Entity Framework
with another technology, but the application layer will still use the same interfaces.
Let’s create a Folder Interfaces and start creating them.
First, we need to create a Generic Repository interface for the Generic Repository Pattern which will be used by all repositories.
public interface IGenericRepository < T > where T: class {
T GetById(int id);
IEnumerable < T > GetAll();
IEnumerable < T > Find(Expression < Func < T, bool >> expression);
void Add(T entity);
void AddRange(IEnumerable < T > entities);
void Remove(T entity);
void RemoveRange(IEnumerable < T > entities);
}
And now an interface for each model class. We keep them empty, for now
public interface IPersonRepository: IGenericRepository < Person > {}
public interface IAdressRepository: IGenericRepository < Address > {}
public interface IEmailRepository: IGenericRepository < Email > {}
And Unit of Work interface, which will be the connection layer between the WebAPI project and the repositories.
ublic interface IUnitOfWork: IDisposable {
IAdressRepository Address {
get;
}
IEmailRepository Email {
get;
}
IPersonRepository Person {
get;
}
int Save();
}
Save method was included here because we need to save changes to the database regardless of which repository have been changed.
After we added all the interfaces this how the project will look
Implement Repository’s and Unit of Work
Let’s go to DataAccessEF project to implement the repositories. We will start with the Generic Repository. We will create a class GenericRepository which will implement IGenericRepository.
This class will contain the implementation for all generic usages.
public class GenericRepository < T > : IGenericRepository < T > where T: class {
protected readonly PeopleContext context;
public GenericRepository(PeopleContext context) {
this.context = context;
}
public void Add(T entity) {
context.Set < T > ().Add(entity);
}
public void AddRange(IEnumerable < T > entities) {
context.Set < T > ().AddRange(entities);
}
public IEnumerable < T > Find(Expression < Func < T, bool >> expression) {
return context.Set < T > ().Where(expression);
}
public IEnumerable < T > GetAll() {
return context.Set < T > ().ToList();
}
public T GetById(int id) {
return context.Set < T > ().Find(id);
}
public void Remove(T entity) {
context.Set < T > ().Remove(entity);
}
public void RemoveRange(IEnumerable < T > entities) {
context.Set < T > ().RemoveRange(entities);
}
}
As you can see here are all the implementation for basic CRUD operations which are used by all repositories. DRY π
Now let’s create a folder named TypeRepository and add the implementation for Address, Email and Person repositories.
class PersonRepository: GenericRepository < Person > , IPersonRepository {
public PersonRepository(PeopleContext context): base(context) {}
}
class AddressRepository: GenericRepository < Address > , IAdressRepository {
public AddressRepository(PeopleContext context): base(context) {}
}
class EmailRepository: GenericRepository < Email > , IEmailRepository {
public EmailRepository(PeopleContext context): base(context) {}
}
All 3 classes are similar, only the types have changed, for now we will keep these classes as they are.
Now Unit of Work. We will create a folder named UnitOfWork and add the implementation class here.
public class UnitOfWork: IUnitOfWork {
private PeopleContext context;
public UnitOfWork(PeopleContext context) {
this.context = context;
Address = new AddressRepository(this.context);
Email = new EmailRepository(this.context);
Person = new PersonRepository(this.context);
}
public IAdressRepository Address {
get;
private set;
}
public IEmailRepository Email {
get;
private set;
}
public IPersonRepository Person {
get;
private set;
}
public void Dispose() {
context.Dispose();
}
public int Save() {
return context.SaveChanges();
}
}
If all the was ok you should have something similar like this
Usage
Let's go to WebAPI project and start to implement the usage.
Add an injectable instance of UnitOfWork class in ConfigureServices () method from Startup.cs class.
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext < PeopleContext > (options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
services.AddTransient < IUnitOfWork, UnitOfWork > ();
Now let’s modify default WeatherForecast controller to a Person controller and inject UnitOfWork instance
[ApiController]
[Route("[controller]")]
public class PersonController: ControllerBase {
private readonly IUnitOfWork unitOfWork;
public PersonController(IUnitOfWork unitOfWork) {
this.unitOfWork = unitOfWork;
}
[HttpGet]
public IEnumerable < Person > GetAllPersons() {
return unitOfWork.Person.GetAll();
}
}
I have created a Get method which is returning all the person from the database. The controller knows only unit of work object and that’s it.
The person repository is inheriting Generic Repository which have implemented GetAll() method. This is an example of the usage Unit of Work with a Generic Repository
Extension to Repository
Let's go to Domain project, and edit IPersonRepository interface to add a method which returns all adult persons.
public interface IPersonRepository: IGenericRepository < Person > {
IEnumerable < Person > GetAdultPersons();
}
Now let's go to DataAccessEF project and add the implementation for the method
class PersonRepository: GenericRepository < Person > , IPersonRepository {
public PersonRepository(PeopleContext context): base(context) {}
IEnumerable < Person > IPersonRepository.GetAdultPersons() {
return context.Person.Where(pers => pers.Age >= 18).ToList();
}
}
Finally lest use our new method in PersonController class, by adding a new GET method
public class PersonController: ControllerBase {
private readonly IUnitOfWork unitOfWork;
public PersonController(IUnitOfWork unitOfWork) {
this.unitOfWork = unitOfWork;
}
[HttpGet]
public IEnumerable < Person > GetAllPersons() {
return unitOfWork.Person.GetAll();
}
[Route("[action]")]
[HttpGet]
public IEnumerable < Person > GetAdultPersons() {
return unitOfWork.Person.GetAdultPersons();
}
}
This is it. π
Conclusion
I strongly recommend to use this type of approach when you have more than 3 tables in your project because sooner than later it will become a headache to keep track with all the repository’s and don’t forget, DRY principle.