Accelerating SQL Bulk Inserts Using C# and EF Core

Fast SQL Bulk Inserts

Fast SQL Bulk Inserts With C# and EF Core

Whether you're building a data analytics platform, migrating a legacy system, or onboarding a surge of new users, there will likely come a time when you'll need to insert a massive amount of data into your database.

Inserting the records one by one feels like watching paint dry in slow motion. Traditional methods won't cut it.

So, understanding fast bulk insert techniques with C# and EF Core becomes essential.

Options for Bulk Inserts

In today's issue, we'll explore several options for performing bulk inserts in C#:

  • Dapper
  • EF Core
  • EF Core Bulk Extensions
  • SQL Bulk Copy

Code: User Class


public class User
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
}

This isn't a complete list of bulk insert implementations. There are a few options I didn't explore, like manually generating SQL statements and using Table-Valued parameters.

EF Core Simple Approach


using var context = new ApplicationDbContext();

foreach (var user in GetUsers())
{
    context.Users.Add(user);

    await context.SaveChangesAsync();
}

Let's start with a simple example using EF Core. We're creating an ApplicationDbContext instance, adding a User object, and calling SaveChangesAsync. This will insert each record to the database one by one. In other words, each record requires one round trip to the database.

Output: EF Core Simple Approach

The results are as poor as you'd expect:

  • EF Core - Add one and save, for 100 users: 20 ms
  • EF Core - Add one and save, for 1,000 users: 260 ms
  • EF Core - Add one and save, for 10,000 users: 8,860 ms

I omitted the results with 100,000 and 1,000,000 records because they took too long to execute.

Dapper Simple Insert


using var connection = new SqlConnection(connectionString);
connection.Open();

const string sql =
    @"
    INSERT INTO Users (Email, FirstName, LastName, PhoneNumber)
    VALUES (@Email, @FirstName, @LastName, @PhoneNumber);
    ";

await connection.ExecuteAsync(sql, GetUsers());

Dapper is a simple SQL-to-object mapper for .NET. It allows us to easily insert a collection of objects into the database.

Output: Dapper Simple Insert

The results are much better than the initial example:

  • Dapper - Insert range, for 100 users: 10 ms
  • Dapper - Insert range, for 1,000 users: 113 ms
  • Dapper - Insert range, for 10,000 users: 1,028 ms

EF Core Add All and Save


using var context = new ApplicationDbContext();

foreach (var user in GetUsers())
{
    context.Users.Add(user);
}

await context.SaveChangesAsync();

However, EF Core still didn't throw in the towel. The first example was poorly implemented on purpose. EF Core can batch multiple SQL statements together, so let's use that.

Output: EF Core Add All and Save

Here are the benchmark results of this implementation:

  • EF Core - Add all and save, for 100 users: 2 ms
  • EF Core - Add all and save, for 1,000 users: 18 ms
  • EF Core - Add all and save, for 10,000 users: 203 ms

EF Core AddRange and Save


using var context = new ApplicationDbContext();

context.Users.AddRange(GetUsers());

await context.SaveChangesAsync();

This is an alternative to the previous example. Instead of calling Add for all objects, we can call AddRange and pass in a collection.

Output: EF Core AddRange and Save

The results are very similar to the previous example:

  • EF Core - Add range and save, for 100 users: 2 ms
  • EF Core - Add range and save, for 1,000 users: 18 ms
  • EF Core - Add range and save, for 10,000 users: 204 ms

EF Core Bulk Extensions


using var context = new ApplicationDbContext();

await context.BulkInsertAsync(GetUsers());

There's an awesome library called EF Core Bulk Extensions that we can use to squeeze out more performance.

Output: EF Core Bulk Extensions

The performance is equally amazing:

  • EF Core - Bulk Extensions, for 100 users: 1.9 ms
  • EF Core - Bulk Extensions, for 1,000 users: 8 ms
  • EF Core - Bulk Extensions, for 10,000 users: 76 ms

SQL Bulk Copy


using var bulkCopy = new SqlBulkCopy(ConnectionString);

bulkCopy.DestinationTableName = "dbo.Users";

bulkCopy.ColumnMappings.Add(nameof(User.Email), "Email");
bulkCopy.ColumnMappings.Add(nameof(User.FirstName), "FirstName");
bulkCopy.ColumnMappings.Add(nameof(User.LastName), "LastName");
bulkCopy.ColumnMappings.Add(nameof(User.PhoneNumber), "PhoneNumber");

await bulkCopy.WriteToServerAsync(GetUsersDataTable());

Last but not least, if we can't get the desired performance from EF Core, we can try using SqlBulkCopy.

Output: SQL Bulk Copy

However, the performance is blazing fast:

  • SQL Bulk Copy, for 100 users: 1.7 ms
  • SQL Bulk Copy, for 1,000 users: 7 ms
  • SQL Bulk Copy, for 10,000 users: 68 ms

Results and Takeaway

Here are the results for all the bulk insert implementations:

Method Size Speed

SqlBulkCopy holds the crown for maximum raw speed. However, EF Core Bulk Extensions deliver fantastic performance while maintaining the ease of use that Entity Framework Core is known for.

The best choice hinges on your project's specific demands:

  • If performance is all that matters, SqlBulkCopy is your solution.
  • If you need excellent speed and streamlined development, EF Core is a smart choice.

Comments

Popular posts from this blog

IList vs ICollection vs IEnumerable

Best practices for exception handling in C#”

1.Introduction to .NET Core and its architecture