Monday, August 31, 2020

EFCore Repository Implementation in ASP.NET Core

 

In this article, we will learn how to create a Repository pattern for Relational databases like SQL in the .NET Core or ASP.NET Core application.

We already discussed basic details on getting started with EFCore in ASP.NET Core in our last article.

In Today’s article, we shall see how to extend the same existing implementation to use better pattern i.e Repository.

Below are the steps which we shall be following to create a Repository,

EFCore scaffolding as Repository and UoW

EFCore is an ORM-Object-relational mapping framework that helps to represent the Database into the object-oriented programming model in the .NET Core ecosystem helping to interact and performing CRUD operation on relational DB without hassle.

Repository design patterns fit into any NoSQL or Relational DB requirements and also can be used for multiple other requirements.

Entity Framework DBContext as Repository and UOW

Using tools like EFCore gives us DBContext which already represents repository and UoW(Unit Of Work) implementation with very minimal effort.

The best thing is you can use DBContext anywhere from your code.

This DBContext can be DI (Dependency Injected ) from API pipeline or Middleware as needed or can be consumed directly.

However, if you see DBContext is a class and not an Interface. If you need to perform Test-Driven Development like Unit Testing, you will find a few challenges in mocking DBContext to simulate the Data access. So considering testability as important principles, one can easily implement Repository.

Repository encourages a more loosely coupled approach to access our data from the database. The code becomes cleaner and maintainable and highly extensible.

Create Models using Schema

Do you follow schema modeling??.

EFCore works very well for Database First approach. Where it lets you create the required scaffolding and domain entities using the existing Database.

However, You can follow the code-first approach as well followed by using Migration and Update database commands to create the database.

EFCore using Database First

We shall be learning EFCore using Database first approach where we already have an existing Database called ‘Master‘ and a table name called Employee

SQL Database Schema

We can start with the below simple Database schema,

I have discussed how to do ECore scaffolding and generate domain entities in the below article.

Once you are ready with scaffolding please use the repository to access DBContext objects as below.

Defining Repository Interfaces

Repository Interfaces will be defined as below,

1
2
3
4
5
6
7
8
9
public interface IEmployeeRepository
{
    IEnumerable<Employee> GetEmployees();
    Employee GetEmployeeByID(int employeeID);
    void InsertEmployee(Employee employee);
    void DeleteEmployee(int employeeID);
    void UpdateEmployee(Employee employee);
 
}

We shall define repository class specific to the context, i.e here EmployeeRepository will be dealing with Employee DBContext.

Define Repository Class

Please see below the implementation for the Repository class performing CRUD operation as below.

EmployeeContext object is accessed using EmployeeRepository Constructor injections as below,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class EmployeeRepository : IEmployeeRepository
   {
       private readonly EmployeeContext _context;
 
       public EmployeeRepository(EmployeeContext context)
       {
           _context = context;
       }
 
       public IEnumerable<Employee> GetEmployees()
       {
           return _context.Employee.ToList();
       }
 
       public Employee GetEmployeeByID(int employeeID)
       {
           return _context.Employee.Find(employeeID.ToString());
       }
 
       public void InsertEmployee(Employee employee)
       {
           _context.Employee.Add(employee);
       }
 
       public void DeleteEmployee(int employeeID)
       {
           Employee employee = _context.Employee.Find(employeeID);
           _context.Employee.Remove(employee);
       }
 
       public void UpdateEmployee(Employee employee)
       {
           _context.Entry(employee).State = EntityState.Modified;
       }
   }

EmployeeContext is created using database Scaffolding DBContext Commands as discussed in our Getting Started using EFCore article.

Now since our Repository is ready, this can be integrated into the code. If following non-DDD or DDD architecture this repository will be responsible for performing CRUD and will also be used for data persistence through DBContext.

Using a Repository in API/Service

Here is an example of how I am using the repository in EmployeeController (Similar way this repository can be interfaced from Domain or Business layer as required (if you have any).

EmployeeRepository object is accessed using EmployeeController’s Constructor injections.

Below is very simple and minimal implementation,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
    private readonly IEmployeeRepository _employeeRepository;
 
    public EmployeeController(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }
 
    // GET: api/Employee
    [HttpGet]
    public ActionResult<IEnumerable<Employee>> Get()
    {
        return Ok(_employeeRepository.GetEmployees());
    }
 
    // GET: api/Employee/5
    [HttpGet("{id}")]
    public ActionResult Get(int id)
    {
        return Ok(_employeeRepository.GetEmployeeByID(id));
    }
 
    // POST: api/Employee
    [HttpPost]
    public void Post([FromBody] Employee value)
    {
        _employeeRepository.InsertEmployee(value);
    }
 
 
    // DELETE: api/5
    [HttpDelete("{id}")]
    public void Delete(int employeeID)
    {
        _employeeRepository.DeleteEmployee(employeeID);
    }
}

Repository and DBContext in IoC Container

Please initialize Repository and DBContext in the Service Container as below,

1
2
3
4
5
6
7
8
9
10
11
12
13
public void ConfigureServices(IServiceCollection services)
       {
           services.AddControllers();
           services.AddScoped<IEmployeeRepository, EmployeeRepository>();
           services.AddDbContext<EmployeeContext>(options =>
           {
               options.UseSqlServer(Configuration.GetConnectionString("EmployeeDB"),
                sqlServerOptionsAction: sqlOptions =>
                {
                    sqlOptions.EnableRetryOnFailure();
                });
           });
       }

Please note that the repository instance lifetime in your IoC container should be set scoped (same as DBContext) as a good practice.

As shown in the above code, we also have resiliency implemented for SQL Connection using EnableRetryOnFailure() added with DBContext instance.

As a good practice, implement resiliency in SQL operation to address any issues related to Transient errors.

One can also implement a more Generic repository around DBContext if needed addressing multiple Domain models as discussed in the below article if needed.

You are all set to use a repository into your code.

That’s all! Happy Coding!

Other References :

Do you see any improvements to the above code? Please sounds off your comments below.

Summary

Today in this article we learned how to implement a Repository around SQL database using the EFCore ORM framework. Application with a complex business/domain model gets a huge advantage from the Repository. It provides an abstraction that not only isolates the business objects from the database access code but also provides a clean separation of concerns in an Object-oriented way.

Thank you for reading. Please let me know your questions, thoughts or feedback below in the comments section. I appreciate your feedback and encouragement.

Please share this article with your friends and Subscribe to the blog to get a notification on freshly published best practices of software development.


Guidance for the Implementation of Repository Pattern and Unit of Work with ASP.NET Core Application

 

Introduction

When implementing repository pattern after learning, I encountered many problems for proper implementation. But I didn't find a full solution anywhere for the proper implementation. This pushed me to write an article.

This article will guide you through creating a small application using repository pattern with Unit of Work in ASP.NET Core. This article is basically targeted at beginner to intermediate level programmers. In this article, I want to provide an overall picture of the implementation.

Here, I don't want to provide details for generic repository pattern implementation. Whenever I search for the repository pattern implementation, I came across lots of samples with the generic repository pattern.

After completing this article, you will get a proper understanding of the implementation for specific repository pattern.

Repository Pattern

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.

Repository pattern is useful when we want to encapsulate the logic to access data source. Here, Repository describes class or component to access the data source.

The repository acts as a mediator between the data source and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source.

Why do we want to encapsulate?

When we want to decouple data access functionality from the business layer, then we move to Repository pattern.

Generic Repository is useful when it defines generic methods for the most common types of data operation, such as updating, fetching and deleting.

In some scenarios, we may not need a common operation for all type of repositories.

So we need specific repositories. This is based on the project that we are going to implement.

In the Repository pattern implementation, Business logic to Data access logic and API to Business Logic talk to each other using interfaces. Data access layer hides the details of data access from the business logic. In detail notation, business logic can access the data access layer without having knowledge of data source.

For example, the Business layer does not know whether the Data access layer uses LINQ to SQL or ADO.NET, etc.

Advantages

The following are key benefits of the Repository pattern.

Isolate Data Access Logic

Data access functionalities are centralised. So the business layer will not have knowledge about where the data comes from. It may come from any data source or cache or mock data.

Unit Testing

Based on the previous, this would understand that the business layer doesn't have knowledge about where the data comes from. It's easy to mock the data access layer. So this will help us to write Unit test for the business logic.

Can't we write any test for the Data Access Layer? Why not? We can write an integration test for this layer.

Caching

Since Data Access functionalities are centralised, we can implement caching for this layer.

Data Source Migration

We can easily migrate from one data source to another data source. This will not affect our business logic when we migrate.

Complex Queries Are Encapsulated

Complex queries are encapsulated and moved to this layer. So queries are reused from the Business layer.

When any developer is strong in writing queries, she/he can independently work on the queries and another developer can concentrate on the business logic.

Thumb Rule of the Implementation

  • Each repository should be implemented based on Domain and not based on the Database Entity.
  • Each repository should not contact each other.
  • IQueryable should not be a return type of the repository pattern implementation. They should return only the IEnumerable.
  • They should not save/delete/add any data to the database. All the details should do in memory. We may think about how we can do crud operations. Here, the Unit Of Work plays that role. Unit Of Work will save details to database or rollback. What are the advantages of this? This will save multiple transactions that happened in the repository in a single shot.
  • Data Layer should not implement business logic. Business logic should be implemented in the Business Layer. They should return the representation of data and business layer should encapsulate return or decapsulate requst.

Project Structure

The following is our project structure that we are going to implement. Please download the sample from the link. Here PL uses Angular application. ASP.NET Core has been used for API and the Business Layer, then Data Access Layer.

Business Layer and Data Access Layer will have separate contracts (interfaces). Business Layer and Data Access Layer will depend on the abstraction not with the concrete implementation.

This is because of Dependency Injection. So no layer will have knowledge about another layer. This is easy when we mockup and do testing.

  • Presentation Layer (PL)
  • API
  • Business Layer (BL)
  • Data Access Layer (DAL)

Please refer to the following image for the Application Flow. PL will contact API. API will contact BL. BL will contact DAL.

We are going to do a loosely coupled implementation. Business Layer will not know about the data access layer. API will not know about the BL.

For this implementation, we are going to implement Dependency Injection (DI).

Dependency Injection (DI)

What is dependency injection?

The higher level module should not depend on the lower level module. Dependency Injection is mainly for injecting the concrete implementation into a class that is using abstraction, i.e., interface inside. This enables the development of the loosely coupled code.

In detail, if your ClassA needs to use a ClassB, make our ClassA aware of an IClassB interface instead of a ClassB. Through this execution, we can change the implementation of the ClassB many times without breaking the host code.

Advantages of DI

  1. Clean and more readable code
  2. Classes or Objects are loosely coupled
  3. Mocking object is easy

Image 1

Using the Code

Consider the following sample for this implementation.

  • CRUD operation for the user
  • CRUD operation for the product
  • Add or remove product to/from user. Only one product can be assigned to the user.

Data Access Layer

Now we have to identify the domains for the problem. Based on the above sample, we identified two Domains.

  1. User Domain
  2. Product Domain

Based on the thumb rule, we need to create a repository based on the domain. So in this sample, we are going to create two repositories for the above two domains:

  1. User repository
  2. Product repository

To create UserRepository and ProductRepository, create classes that will implement the repository interface IUserRepositoryIProductRepository respectively.

IUserRepository

public interface IUserRepository
    {
        void AddUser(User user);
        IEnumerable<User> GetUsers();
        bool DeleteUser(long userId);
        User GetUser(long Id);
    }

IProductRepository

public interface IProductRepository
   {
       void AddProduct(Product product);
       Product GetProduct(long id);
       IEnumerable<Product> GetProducts();
       bool DeleteProduct(long productId);
       IEnumerable<Product> GetUserProducts(long userId);
       void AddProductToUser(long userId, long productId);
   }

Now create concrete classes that will implement the abstractions, i.e., interface.

These concrete classes will have the actual implementation. Here, we can notice that:

  • Every Add or delete is implemented in memory, not in the data source
  • There is no update to the Data source.

UserRepository

public class UserRepository : IUserRepository
    {
        private readonly AppDbContext context;

        public UserRepository(AppDbContext dbContext)
        {
            this.context = dbContext;
        }
        public void AddUser(User user)
        {
            context.Users.Add(user);
        }

        public bool DeleteUser(long userId)
        {
            var removed = false;
            User user = GetUser(userId);

            if (user != null)
            {
                removed = true;
                context.Users.Remove(user);
            }

            return removed;
        }

        public User GetUser(long Id)
        {
            return context.Users.Where(u => u.Id == Id).FirstOrDefault();
        }

        public IEnumerable<User> GetUsers()
        {
            return context.Users;
        }
    }

ProductRepository

public class ProductRepository : IProductRepository
    {
        private readonly AppDbContext context;

        public ProductRepository(AppDbContext dbContext)
        {
            this.context = dbContext;
        }

        public void AddProduct(Product product)
        {
            context.Products.Add(product);
        }

        public void AddProductToUser(long userId, long productId)
        {
            context.UserProducts.Add(new UserProduct()
            {
                ProductId = productId,
                UserId = userId
            });
        }

        public bool DeleteProduct(long productId)
        {
            var removed = false;
            Product product = GetProduct(productId);
            if (product != null)
            {
                removed = true;
                context.Products.Remove(product);
            }

            return removed;
        }

        public Product GetProduct(long id)
        {
            return context.Products.Where(p => p.Id == id).FirstOrDefault();
        }

        public IEnumerable<Product> GetProducts()
        {
            return context.Products;
        }

        public IEnumerable<Product> GetUserProducts(long userId)
        {
            return context.UserProducts
                  .Include(up => up.Product)
                  .Where(up => up.UserId == userId)
                  .Select(p => p.Product)
                  .AsEnumerable();
        }
    }

Unit Of Work(UOW)

From the above implementation, we can understand that the repository should be used:

  • to read data from the data source
  • to add/remove data in memory

Then how the add/update/delete will affect the data source? Here the UOW plays that role. UOW knows about each repository. This helps to achieve multiple transactions at a time.

For this implementation, need to achieve as above. Create a concrete UnitOfWork that will implement the abstraction, i.e., interface IUnitOfWork.

IUnitOfWork

public interface IUnitOfWork
   {
       IUserRepository User { get; }
       IProductRepository Product { get; }
       Task<int> CompleteAsync();
       int Complete();
   }

UnitOfWork

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext dbContext;
    public UnitOfWork(AppDbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    private IUserRepository _User;

    private IProductRepository _Product;
    public IUserRepository User
    {
        get
        {
            if (this._User == null)
            {
                this._User = new UserRepository(dbContext);
            }
            return this._User;
        }
    }
    public IProductRepository Product
    {
        get
        {
            if (this._Product == null)
            {
                this._Product = new ProductRepository(dbContext);
            }
            return this._Product;
        }
    }

    public async Task<int> CompleteAsync()
    {
        return await dbContext.SaveChangesAsync();
    }
    public int Complete()
    {
        return dbContext.SaveChanges();
    }
    public void Dispose() => dbContext.Dispose();

}

We have done repository pattern implementation with UOW for the DAL.

The following would be silly. After doing this, I had a confusion about how we need to get data from another repository to check before saving data. For example, when adding the product to the user, check whether the user or product exists or not.

This scenario will violate the rule, i.e., repositories should not interact within them. What happened? What should I do now? Here, my understanding was wrong. Business logic should not present in the repository pattern. This is only an encapsulation of data access. Every logic validations should be moved to Business Layer. Business Layer will know about all the repository that will take care of the validation.

Business Layer

Now we need to concentrate on the Business Layer. In this layer, we are going to inject the UOW instead of all the necessary repositories. UOW knows about all the repositories and we can access using UOW.

For example, to implement the Product's BL, we are going to create an interface IProduct and need to create a concrete class BLProduct that will implement the IProduct.

Below in BLProduct, all the necessary validations and business logic have been done and we can notice in AddProductToUser method as an example for the multiple repository usages.

IProduct

public interface IProduct
    {
        Product UpsertProduct(Product product);
        IEnumerable<Product> GetProducts();
        bool DeleteProduct(long productId);
        IEnumerable<Product> GetUserProducts(long userId);
        bool AddProductToUser(long userId, long productId);
    }

BLProduct

public class BLProduct : IProduct
   {
       private readonly IUnitOfWork uow;
       public BLProduct(IUnitOfWork uow)
       {
           this.uow = uow;
       }

       public bool AddProductToUser(long userId, long productId)
       {
           if (userId <= default(int))
               throw new ArgumentException("Invalid user id");
           if (productId <= default(int))
               throw new ArgumentException("Invalid product id");

           if (uow.Product.GetProduct(productId) == null)
               throw new InvalidOperationException("Invalid product");

           if (uow.User.GetUser(userId) == null)
               throw new InvalidOperationException("Invalid user");

           var userProducts = uow.Product.GetUserProducts(userId);

           if (userProducts.Any(up => up.Id == productId))
               throw new InvalidOperationException("Products are already mapped");

           uow.Product.AddProductToUser(userId, productId);
           uow.Complete();

           return true;
       }

       public bool DeleteProduct(long productId)
       {
           if (productId <= default(int))
               throw new ArgumentException("Invalid produt id");

           var isremoved = uow.Product.DeleteProduct(productId);
           if (isremoved)
               uow.Complete();

           return isremoved;
       }

       public IEnumerable<Product> GetProducts()
       {
           // May implement role based access
           return uow.Product.GetProducts();
       }

       public IEnumerable<Product> GetUserProducts(long userId)
       {
           if (userId <= default(int))
               throw new ArgumentException("Invalid user id");

           return uow.Product.GetUserProducts(userId);
       }

       public Product UpsertProduct(Product product)
       {
           if (product == null)
               throw new ArgumentException("Invalid product details");

           if (string.IsNullOrWhiteSpace(product.Name))
               throw new ArgumentException("Invalid product name");

           var _product = uow.Product.GetProduct(product.Id);
           if (_product == null)
           {
               _product = new Product
               {
                   Name = product.Name
               };
               uow.Product.AddProduct(_product);
           }
           else
           {
               _product.Name = product.Name;
           }

           uow.Complete();

           return _product;
       }
   }

Here in AddProductToUser method, I want to add a product to a user. So before adding product to the user, I have done the following validations in the method:

  • Parameter validations
  • Check whether the product is deleted or not
  • Check whether the user exists or not
  • Check whether the product is already added to the user or not
  • Finally, add the product to the collections

After doing the above steps, finally, save the user product.

In UpsertProduct method, we are going to achieve add or update. If the product is not available, then add. If the product is available, then update. For this:

  • Need to check for the valid values
  • Then try to get the product and check product is available
  • If it is not available, then add to the collection
  • If it is available, then update the necessary value in the collection

After doing the above, then save the values.

What does it mean? It helps to control when we can do save values. We did not save immediately when we are adding or updating. We can do many more operations here, then finally we can save.

API

As we are in the flow, we can see that we have done the DAL and BL. Now we inject the BL in the API and do the necessary action.

Here, I am using the ASP.NET CORE. We need to register the dependency in service container as below:

// Inject BL
    services.AddScoped<IUser, BLUser>();
    services.AddScoped<IProduct, BLProduct>();
    // Inject unit of work
    services.AddScoped<IUnitOfWork, UnitOfWork>();

After registration, we need to inject this dependency in the controller. Please refer to the below code.

ProductController

[Route("api/Product")]
[ApiController]
public class ProductController : ControllerBase
{
    private readonly IMapper mapper;
    private readonly IProduct blProduct;

    public ProductController(IMapper mapper, IProduct product)
    {
        this.mapper = mapper;
        this.blProduct = product;
    }

    // GET: api/Product
    [HttpGet]
    public IEnumerable<ProductModel> Get()
    {
        var products = blProduct.GetProducts();
        return mapper.Map<IEnumerable<Product>, IEnumerable<ProductModel>>(products);
    }

    // GET: api/Product/5
    [HttpGet("{id}")]
    public IEnumerable<ProductModel> Get(int userId)
    {
        var products = blProduct.GetUserProducts(userId);
        return mapper.Map<IEnumerable<Product>, IEnumerable<ProductModel>>(products);
    }

    // POST: api/Product
    [HttpPost]
    public void Post([FromBody] ProductModel product)
    {
    }

    // DELETE: api/ApiWithActions/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {
    }
}

Controversy

When I start to learn and implement the repository pattern, I found many articles that we should not implement Repository pattern with Entity Framework (EF).

Why?

Because EF is implemented with the Repository Pattern and Unit of Work. Why do we need a Layer to another layer which is implementing with the same pattern?

Yeah, this sounds good. Right?

My Conclusion

Yeah, the above is a good point. After thinking about the following, I have concluded that we are not wrong when implementing the repository pattern with the EF.

  • In future, if we are going to migrate the ORM for any kind of issue, then our implementation part gives a better solution for the migration.
  • We can move our complex and bulk queries inside the DAL.
  • When we are going to do Unit test, then this implementation gives an easy way to mock the DAL.
  • We can concentrate only on the DAL for Caching implementation.

Points of Interest

When I start to implement the repository pattern, I did not find proper guidance. I hope the article will provide a proper idea for the developer who is seeking the proper way of implementation.