Utilizing Bulk Operations with Entity Framework and ASP.NET Core

6 minute read

A bulk operation refers to the execution of a single operation that involves multiple data entries or rows in a database, typically performed in a batch. Instead of processing each entry individually, bulk operations allow for the manipulation or retrieval of data in a more efficient manner.

The benefits of bulk operations include:

  1. Improved Performance: By processing multiple data entries in a single operation, bulk operations can significantly reduce the overhead associated with multiple individual operations, resulting in improved performance and reduced latency.

  2. Reduced Resource Usage: Bulk operations require fewer resources compared to performing the same task iteratively on each data entry. This can lead to better utilization of system resources and improved scalability.

  3. Enhanced Throughput: With the ability to handle large volumes of data more efficiently, bulk operations can increase the throughput of data processing tasks, allowing applications to handle higher workloads without sacrificing performance.

  4. Minimized Database Overhead: Bulk operations can help minimize the overhead on the database server by reducing the number of transactions and communication overhead associated with processing individual data entries.

  5. Simplified Code Logic: By consolidating multiple operations into a single bulk operation, developers can write simpler and more concise code, leading to easier maintenance and reduced complexity in application logic.

To perform bulk operations in an ASP.NET Core application, we can leverage the capabilities of Entity Framework Core and the EFCore.BulkExtensions library. This article will explore the practical implementation of bulk operations using these tools, providing insights into how to optimize database operations and enhance application performance.

EFCore BulkExtensions

EFCore.BulkExtensions is a popular library that extends the functionality of Entity Framework Core by providing support for bulk operations such as bulk insert, update, delete, and merge. This library offers a convenient and efficient way to perform bulk operations on database entities, allowing developers to streamline data processing tasks and improve application performance.

In this article, we will focus on the following bulk operations using EFCore.BulkExtensions and Entity Framework Core.

Tools and Technology Used

  • ASP.net core Web API
  • Visual C#
  • Entity Framework
  • EFCore.BulkExtensions
  • SQL Server

Step 1: Create a asp.net core web api project name EFBulkBatch

Step 2: Install the following nuget packages in the project.

EFCore.BulkExtensions
Microsoft.AspNetCore.OpenApi
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools

Step 3: Create two Model class name Customer and Employee in Models folder

  • Customer class will be used to perform bulk operation using Entity Framework.
  • Employee class will be used to perform bulk operation using EFCore.BulkExtensions.

Customer.cs


namespace EFBulkBatch.Models
{
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }

    }
}

Employee.cs


namespace EFBulkBatch.Models
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Designation { get; set;}
        public string Department { get; set; }

    }
}

Step 4: Create Db Context class name ApplicationDbContext in Db folder.

ApplicationDbContext.cs


using EFBulkBatch.Models;
using Microsoft.EntityFrameworkCore;

namespace EFBulkBatch.Db
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
            
        }
        
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Customer> Customers { get; set; }
    }
}


Step 5: Create CustomerService class in Manager folder to perform bulk operation using Entity Framework.

Manager/CustomerService.cs

using EFBulkBatch.Db;
using EFBulkBatch.Models;

namespace EFBulkBatch.Managers
{
    public class CustomerService
    {
        private readonly ApplicationDbContext _context;
        private DateTime _startTime;
        private TimeSpan _elapsedTime;
        public CustomerService(ApplicationDbContext context) 
        {
            _context = context;
        }


        public async Task<TimeSpan> AddBulkCustomerAsync()
        {
            // C# 9.0 feature: Target-typed new expressions
            List<Customer> customers = new();
            _startTime = DateTime.Now;

            for (int i = 0; i < 100000; i++)
            {
                customers.Add(new Customer
                {
                    Name = $"Customer {i}",
                    Email = $"Email {i}",
                    Phone = $"Phone {i}"
                });
            }

            // Use AddRange to add multiple entities
            _context.Customers.AddRange(customers);
            await _context.SaveChangesAsync();
            _elapsedTime = DateTime.Now - _startTime;
            return _elapsedTime;
        }

        public async Task<TimeSpan> UpdateBulkCustomerAsync()
        {
            // C# 9.0 feature: Target-typed new expressions
            List<Customer> customers = new();
            _startTime = DateTime.Now;
            for (int i = 0; i < 100000; i++)
            {
                customers.Add(new Customer
                {
                    Id = i + 1,
                    Name = $"Update Customer {i}",
                    Email = $"Update Email {i}",
                    Phone = $"Update Phone {i}"
                });
            }

            // Use UpdateRange to update multiple entities
            _context.Customers.UpdateRange(customers);
 
            await _context.SaveChangesAsync();

            _elapsedTime = DateTime.Now - _startTime;
            return _elapsedTime;
        }

        public async Task<TimeSpan> DeleteBulkCustomerAsync()
        {
            List<Customer> customers = _context.Customers.ToList();
            _startTime = DateTime.Now;

            // Use RemoveRange to delete multiple entities
            _context.Customers.RemoveRange(customers);
            await _context.SaveChangesAsync();
            _elapsedTime = DateTime.Now - _startTime;
            return _elapsedTime;
        }
    }
}

Step 6: Create EmployeeService class in Manager folder to perform bulk operation using EFCore.BulkExtensions.

Manager/EmployeeService.cs

using EFBulkBatch.Db;
using EFBulkBatch.Models;
using EFCore.BulkExtensions;

namespace EFBulkBatch.Managers
{
    public class EmployeeService
    {
        private readonly ApplicationDbContext _dbContext;
        private DateTime _startTime;
        private TimeSpan _elapsedTime;

        public EmployeeService(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<TimeSpan> AddBulkDataAsync()
        {
            // C# 9.0 feature: Target-typed new expressions
            List<Employee> employees = new();
            _startTime = DateTime.Now;

            for(int i = 0; i < 100000; i++)
            {
                employees.Add(new Employee
                {
                    Name = $"Employee {i}",
                    Designation = $"Designation {i}",
                    Department = $"Department {i}"
                });
            }

         
            // Use BulkInsertAsync to add multiple entities
            await _dbContext.BulkInsertAsync(employees);
            _elapsedTime = DateTime.Now - _startTime;
            return _elapsedTime;
        }


        public async Task<TimeSpan> UpdateBulkDataAsync()
        {
            // C# 9.0 feature: Target-typed new expressions
            List<Employee> employees = new();
            _startTime = DateTime.Now;
            for(int i = 0; i < 100000; i++)
            {
                employees.Add(new Employee
                {
                    Id = i + 1,
                    Name = $"Update Employee {i}",
                    Designation = $"Update Designation {i}",
                    Department = $"Update Department {i}"
                });
            }

            // Use BulkUpdateAsync to update multiple entities
            await _dbContext.BulkUpdateAsync(employees);
            _elapsedTime = DateTime.Now - _startTime;
            return _elapsedTime;
        }

        public async Task<TimeSpan> DeleteBulkDataAsync()
        {
            // C# 9.0 feature: Target-typed new expressions
            List<Employee> employees = _dbContext.Employees.ToList();
            _startTime = DateTime.Now;

            // Use BulkDeleteAsync to delete multiple entities
            await _dbContext.BulkDeleteAsync(employees);
            _elapsedTime = DateTime.Now - _startTime;
            return _elapsedTime;
        }


    }
}

Step 7: Add connection string in appsettings.json file

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=EFBulkBatchDB; User Id = sa; Password = YourPassword; TrustServerCertificate=True;"
  }
}

Step 8: Configure Program class as follows.

Program.cs


using EFBulkBatch.Db;
using EFBulkBatch.Managers;
using Microsoft.EntityFrameworkCore;

namespace EFBulkBatch
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers();

            // Register the DbContext with the DI container
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                           options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

            // Register the Services with the DI container
            builder.Services.AddScoped<EmployeeService>();
            builder.Services.AddScoped<CustomerService>();


            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();


            app.MapControllers();

            app.Run();
        }
    }
}

Step 9: Create two Controller classes name CustomerController and EmployeeController in Controllers folder.

Controllers/CustomerController.cs

using EFBulkBatch.Managers;
using Microsoft.AspNetCore.Mvc;

namespace EFBulkBatch.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CustomerController : Controller
    {
        private readonly CustomerService _customerService;
        public CustomerController(CustomerService customerService) 
        {
            _customerService = customerService;
        }

        [HttpGet]
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost("AddBulkData")]
        public async Task<IActionResult> AddBulkDataAsync()
        {
            var elapsedTime = await _customerService.AddBulkCustomerAsync();
            return Ok(elapsedTime);
        }

        [HttpPut("UpdateBulkData")] 
        public async Task<IActionResult> UpdateBulkDataAsync()
        {
            var elapsedTime = await _customerService.UpdateBulkCustomerAsync();
            return Ok(elapsedTime);
        }

        [HttpDelete("DeleteBulkData")]
        public async Task<IActionResult> DeleteBulkDataAsync()
        {
            var elapsedTime = await _customerService.DeleteBulkCustomerAsync();
            return Ok(elapsedTime);
        }
    }
}

Controllers/EmployeeController.cs

using EFBulkBatch.Managers;
using EFBulkBatch.Models;
using Microsoft.AspNetCore.Mvc;

namespace EFBulkBatch.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : Controller
    {
        private readonly EmployeeService _employeeService;

        public EmployeeController(EmployeeService employeeService)
        {
            _employeeService = employeeService;
        }

        [HttpGet]
        public IActionResult Index()
        {
            return View();
        }


        [HttpPost("AddBulkData")]
        public async Task<IActionResult> AddBulkDataAsync()
        {
            var elapsedTime = await _employeeService.AddBulkDataAsync();
            return Ok(elapsedTime);
        }

        [HttpPut("UpdateBulkData")]
        public async Task<IActionResult> UpdateBulkDataAysnc()
        {
            var elapsedTime = await _employeeService.UpdateBulkDataAsync();
            return Ok(elapsedTime);

        }

        [HttpDelete("DeleteBulkData")]
        public async Task<IActionResult> DeleteBulkDataAsync()
        {
            var elapsedTime = await _employeeService.DeleteBulkDataAsync();
            return Ok(elapsedTime);
        }
    }
}

Step 10: Run migration command to create database and tables.

  • Go to tools > NuGet Package Manager > Package Manager Console
  • Select EFBulkBatch from Default project dropdown
  • Run the following command in Package Manager Console window
Add-Migration InitialCreate
Update-Database

Step 11: Run the application and test the API using Postman or Swagger UI.

  • Run the application
  • Open Postman or Swagger UI
  • Test the API using the endpoint and perform the bulk operation for insert, update and delete.

Source code