Implement create, read, update, and delete functionalities using Entity Framework Core
Implement create, read, update, and delete functionalities using Entity Framework Core

Introduction

In this post, I show how to Implement CURD functionality in ASPNETCORE using Entity Framework Core. I have my existing project with bare minimum configuration setup to start and to start with this post demo, I will be using it.

To follow along with me for this post, you need to know some basic knowledge on setting up a SQL Database to use as our back end.

To know more, take a look at my post CONFIGURE AZURE SQL RELATIONAL DATABASE USING VS 2019

The full demo application is available for free and you may download from my GitHub repository. Take "feature/ImplementCURDFunctionality" branch for this demo.

Getting Started

Now with the demo application in place, we have setup our back end database which has shopping related entities. I will show you how to implement create, read, update, and delete functionalities using Entity Framework Core.

I will be implementing CURD functionalities for Product entity.

To speed up the development work and also to encapsulate the logic required to access data sources, I will be using Repository pattern in this demo and to my demo project.

What is Repository pattern ?

Repositories are classes or components that encapsulate the logic required to access data sources. The functionality written in the repository is centralized and providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.

As we are using Object-Relational Mapper (ORM) like Entity Framework Core in our project, the code that must be implemented is simplified and it is easy to use.

If you ever think why not to use UnitOfWork pattern along with the repository patter, the answer is NO in my opinion.

This is because, the ORM, in our case Entity Framework Core itself is designed using UnitOfWork design patter as it is tracking all the changes that we make and commits all at once.

It is good idea not to bring in a wrapper for EntityFramework Core.

Repository Setup

Let's have a generic repository which will have most of the common logic that is required to perform CURD operation on a given entity.

 public interface IGenericRepository
    {
        Task> GetAsync(
            Expression> filter = null,
            Func, IOrderedQueryable> orderBy = null,
            string includeProperties = "", CancellationToken cancellationToken = default, bool trackable = true);

        Task GetByIdAsync(object id, CancellationToken cancellationToken = default);

        Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default);
        void Delete(object id);
        void Delete(TEntity entityToDelete);
        void Update(TEntity entityToUpdate);
        void UpdateStateAlone(TEntity entityToUpdate);
        void DetachEntities(TEntity entityToDetach);
        void DetachEntities(List entitiesToDetach);
    }
 public class GenericRepository : IGenericRepository
   where TEntity : class, new()
    {
        private readonly ManageTextDbContext context;
        internal DbSet dbSet;

        public GenericRepository(ManageTextDbContext context)
        {
            this.context = context;
            dbSet = context.Set();
        }

        public virtual Task> GetAsync(
            Expression> filter = null,
            Func, IOrderedQueryable> orderBy = null,
            string includeProperties = "", CancellationToken cancellationToken = default, bool trackable = true)
        {
            IQueryable query = dbSet;

            if (filter != null)
            {
                query = trackable ? query.Where(filter).AsNoTracking() : query.Where(filter).AsNoTracking();
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty).AsNoTracking();
            }

            if (orderBy != null)
            {
                return orderBy(query).AsNoTracking().ToListAsync();
            }
            else
            {
                return query.AsNoTracking().ToListAsync();
            }
        }

        public virtual Task GetByIdAsync(object id, CancellationToken cancellationToken = default)
        {
            return dbSet.FindAsync(id);
        }

        public virtual Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            return dbSet.AddAsync(entity, cancellationToken);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }

        public virtual void UpdateStateAlone(TEntity entityToUpdate)
        {            
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }

        public void DetachEntities(TEntity entityToDetach)
        {
            context.Entry(entityToDetach).State = EntityState.Detached;            
        }

        public void DetachEntities(List entitiesToDetach)
        {
            foreach (var entity in entitiesToDetach)
            {
                context.Entry(entity).State = EntityState.Detached;
            }
        }

    }

Setup repository for Entities

In this demo, I need a repository for Product entity, below is the code to setup IProductRepository.

using KarthikTechBlog.Shopping.Core;
using System.Threading.Tasks;

namespace KarthikTechBlog.Shopping.Data
{
    public interface IProductRepository : IGenericRepository
    {
        Task CommitAsync();
    }
}
using KarthikTechBlog.Shopping.Core;
using System.Threading.Tasks;

namespace KarthikTechBlog.Shopping.Data
{
    public class ProductRepository : GenericRepository, IProductRepository
    {
        private readonly ShoppingDbContext _context;
        public ProductRepository(ShoppingDbContext context) : base(context)
        {
            _context = context;
        }

        public Task CommitAsync()
        {
            return _context.SaveChangesAsync();
        }
    }
}

Create Product Service

Let's create a service for the Product entity and this will access all product related functionality.

using KarthikTechBlog.Shopping.Core;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace KarthikTechBlog.Shopping.Service
{
    public interface IProductService
    {
        Task CreateProductAsync(Product product);
        Task UpdateProductAsync(Product product);
        Task DeleteProductAsync(int productId);
        Task> GetProductsAsync();
        Task GetProductAsync(int productId);
    }
}

using KarthikTechBlog.Shopping.Core;
using KarthikTechBlog.Shopping.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace KarthikTechBlog.Shopping.Service
{
    public class ProductService: IProductService
    {
        public ProductService(IProductRepository productRepository)
        {
            ProductRepository = productRepository;
        }

        public IProductRepository ProductRepository { get; }

        public async Task CreateProductAsync(Product product)
        {
            await ProductRepository.InsertAsync(product);
            return await ProductRepository.CommitAsync();
        }

        public async Task DeleteProductAsync(int productId)
        {
            ProductRepository.Delete(productId);
            return await ProductRepository.CommitAsync();
        }

        public Task GetProductAsync(int productId)
        {
            return ProductRepository.GetByIdAsync(productId);
        }

        public Task> GetProductsAsync()
        {
            return ProductRepository.GetAsync();
        }

        public async Task UpdateProductAsync(Product product)
        {
            ProductRepository.Update(product);
            return await ProductRepository.CommitAsync();
        }
    }
}

Now all we left here is controller, come let's write some minimum code to complete our CURD functionality for Product entity.

Read Product data (GET API)

    public class ProductController : ControllerBase
    {
        public ProductController(IProductService productService)
        {
            ProductService = productService;
        }

        public IProductService ProductService { get; }

        public async Task GetProduct(
           [FromQuery] int productId)
        {
            var product = await ProductService.GetProductAsync(productId);
            return Ok(product);
        }
    }

The controller code is pretty simply, that is the beauty of using right layers to talk to back end database. In this case, we inject the Service layer for Product and call the required methods to get Product data.

Now, one thing in this I would like to change. Currently we are returning the entity model itself from the controller. what if we need to add custom data annotation or custom model validation? I will show you the custom validation in another post.

For now, let's create view model for that we need to send to client which is our API consuming client.

I have created ProductViewModel as the model for any GET API calls.

public class ProductViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public DateTime? AvailableSince { get; set; }
        public int StockCount { get; set; }
        public int CategoryId { get; set; }
    }

Configure AutoMapper

Now, we need to map our Entity properties to custom view model. There are two ways we can do this, one is to manually assign values and other best one is to use AutoMapper.

Add auto mapper package to the API project to setup AutoMapper

AutoMapper Version="8.0.0" AutoMapper.Extensions.Microsoft.DependencyInjection Version="6.0.0"

Configure AutoMapper for the Entities

Add a class AutoMapperConfiguration to the API project and inherit Profile class from AutoMapper. Create configuration for Product and ProductViewModel.

 public class AutoMapperConfiguration : Profile
    {
        public AutoMapperConfiguration()
        {
            CreateMap(); //source, destination
        }
    }

Modified Controller Code

using AutoMapper;
using KarthikTechBlog.Shopping.API.ViewModel;
using KarthikTechBlog.Shopping.Service;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace KarthikTechBlog.Shopping.API.Controllers
{
    public class ProductController : ControllerBase
    {
        public ProductController(IProductService productService, IMapper mapper)
        {
            ProductService = productService;
            Mapper = mapper;
        }

        public IProductService ProductService { get; }
        public IMapper Mapper { get; }

        public async Task GetProduct(
           [FromQuery] int productId)
        {
            var product = await ProductService.GetProductAsync(productId);
            var model = Mapper.Map(product);
            return new OkObjectResult(model);
        }
    }
}

Complete Controller Code to Implement CURD functionality in ASPNETCORE

using AutoMapper;
using KarthikTechBlog.Shopping.API.ViewModel;
using KarthikTechBlog.Shopping.Service;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using KarthikTechBlog.Shopping.Core;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Collections.Generic;

namespace KarthikTechBlog.Shopping.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        public ProductController(IProductService productService, IMapper mapper)
        {
            ProductService = productService;
            Mapper = mapper;
        }

        public IProductService ProductService { get; }
        public IMapper Mapper { get; }

        [HttpGet("{productId}")]
        [ProducesResponseType(typeof(ProductViewModel), StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status404NotFound)]
        public async Task GetProduct(
           [FromRoute] int productId)
        {
            var product = await ProductService.GetProductAsync(productId);

            if (product == null)
                return NotFound();

            var model = Mapper.Map(product);
            return new OkObjectResult(model);
        }

        [HttpGet("")]
        [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status404NotFound)]
        public async Task GetProducts()
        {
            var products = await ProductService.GetProductsAsync();

            var model = Mapper.Map>(products);
            return new OkObjectResult(model);
        }

        [HttpPost("", Name = "CreateProduct")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status400BadRequest)]
        public async Task CreateProduct(
           [FromBody] CreateProduct product)
        {
            var entityToAdd = Mapper.Map(product);

            await ProductService.CreateProductAsync(entityToAdd);

            return Ok();
        }

        [HttpPut("{productId}", Name = "UpdateProduct")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task UpdateProduct(
           [FromRoute] int productId, [FromBody] UpdateProduct product)
        {

            var existingProduct = await ProductService.GetProductAsync(productId);

            if (existingProduct == null)
                return NotFound();

            var entityToUpdate = Mapper.Map(product);

            await ProductService.UpdateProductAsync(entityToUpdate);

            return Ok();
        }


        [HttpDelete("{productId}", Name = "DeleteProduct")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task DeleteProduct(
          [FromRoute] int productId)
        {

            var existingProduct = await ProductService.GetProductAsync(productId);

            if (existingProduct == null)
                return NotFound();

            await ProductService.DeleteProductAsync(productId);

            return Ok();
        }

    }
}

One last thing to include is to configure the DI in the ConfigureServices method of startup class.

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext(options =>
               options.UseSqlServer(Configuration.GetConnectionString("DbContext")));
                        
            services.AddScoped();
            services.AddScoped();
            services.AddAutoMapper();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

Additional Resource

Design the infrastructure persistence layer

Conclusion

In this post, I showed how to implement create, read, update, and delete functionalities using Entity Framework Core. This post is part of learning track for azure AZ-203 certification. I have covered end to end coding that is required to write a good Restful API. In next post, I will show how to create API documentation using OpenAPI & Swagger.

That’s all from this post. If you have any questions or just want to chat with me, feel free to leave a comment below.

One thought on “Implement create, read, update, and delete functionalities using Entity Framework Core

  1. Fantastic goods from you, man. I’ve understand your stuff previous to and you are just too magnificent.
    I actually like what you have acquired here, certainly like what you are
    stating and the way in which you say it. You make it entertaining and you still care for to keep it wise.
    I cant wait to read much more from you. This is
    actually a terrific site.

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights