Accelerating SQL Bulk Inserts Using C# and EF Core
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
Post a Comment