When working with Entity Framework Core, you will eventually need to perform bulk operations on large datasets.
Standard EF Core methods work well for small operations, but they become slow and inefficient when dealing with thousands of records.
Entity Framework Extensions is famous for its fastest bulk operations in the market.
It supports various database providers and provides methods for bulk insert, update, delete, merge, and synchronize operations.
But the real power of this library is not just speed.
Entity Framework Extensions is famous for its hundreds of available options.
These options will save you hours, or even days, of tedious coding that is prone to bugs.
In this post, we will explore the most important customizable options available in Entity Framework Extensions.
You will learn how to fine-tune bulk operations for various real-world scenarios.
Let's dive in.
👉 Read original article on my newsletter: https://antondevtips.com/blog/entity-framework-extensions-options-explained
Performing Bulk Insert with Entity Framework Extensions
I have been working on an interesting project that manages IoT devices and their telemetry data in the SQL Server database.
The database has three main tables:
- Devices - Represent IoT devices with properties like name, serial number, device type, manufacturer, firmware version, hardware version, status, and configuration
- Components - Represent components of these devices, such as sensors, and other hardware parts
- Telemetry - Store telemetry data collected from these devices, including temperature readings, humidity levels, and other sensor values
In our ASP.NET Core application, we have three main entities: Device, Component, and Telemetry.
Imagine a scenario where your system needs to insert 10,000-50,000 telemetry records into the database every few minutes.
You can wait for minutes for this insert to happen with EF Core, but you don't have to.
Entity Framework Extensions solves this problem and provides lightning-fast bulk insert methods.
To get started with the Entity Framework Extensions library, you need to install the following NuGet package:
dotnet add package Z.EntityFramework.Extensions.EFCore
Here's how to bulk insert IoT devices into the database.
The Entity Framework Extensions library provides various extension methods for the DbContext class, such as BulkInsert.
Both async and sync versions of this method are available.
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(10);
await dbContext.BulkInsertAsync(devices);
Entity Framework Extensions provides extensive configuration options to customize the behavior of bulk operations.
Entity Framework Extensions - Bulk Insert Options
The bulk insert method provides several options for customizing behavior. The method accepts an options delegate as a second parameter, allowing you to configure various settings.
Let's explore a few of the most important options.
Entity Framework Extensions - InsertIfNotExists Option
The InsertIfNotExists option allows you to insert only the entities that don't already exist in the database.
To demonstrate this behavior, first insert 10 IoT devices, then attempt to insert the same devices again with InsertIfNotExists enabled:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(10);
await dbContext.BulkInsertAsync(devices);
// Insert the same 10 devices again with the InsertIfNotExists = true option
await dbContext.BulkInsertAsync(devices, options =>
{
options.InsertIfNotExists = true;
});
The second insert operation prevents duplicate entries, ensuring that the 10 devices are not inserted again.
By default, the Entity Framework Extensions library matches the entities by their primary key.
In our case, in the Device entity, it will be the DeviceId.
But you can customize this behavior.
Entity Framework Extensions - Customizing Primary Key with ColumnPrimaryKeyExpression
The ColumnPrimaryKeyExpression option accepts a delegate that defines which property (or properties) to use for matching entities.
This option supports matching by any property or combination of properties. For example, IoT devices can be matched by serial number instead of the default primary key:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(devices, options =>
{
options.InsertIfNotExists = true;
options.ColumnPrimaryKeyExpression = d => d.SerialNumber;
});
Entity Framework Extensions - InsertKeepIdentity Option
The InsertKeepIdentity option allows you to insert custom identity values instead of letting the database generate them automatically. This is particularly useful when the Device entity uses a long type for the primary key in EF Core and a database identity in SQL Server.
Let's try to insert 2 devices with custom IDs:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(2);
devices[0].DeviceId = 1000;
devices[1].DeviceId = 1001;
await dbContext.BulkInsertAsync(devices, options =>
{
options.InsertKeepIdentity = true;
});
The bulk insert operation preserves the custom identity values (1000 and 1001) instead of using database-generated values.
This option is particularly useful when you synchronize data from other services or external providers, and you want to keep your identity values in sync with those systems.
Entity Framework Extensions - AutoMapOutputDirection Option
The AutoMapOutputDirection option controls whether database-generated values are mapped back to the entity objects after insertion. By default, this option is set to true, which means primary keys and other database-generated columns are automatically populated:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(10);
await dbContext.BulkInsertAsync(devices);
// After insertion, devices now have their DeviceId populated
foreach (var device in devices)
{
Console.WriteLine($"Device ID: {device.DeviceId}");
}
The identity values for primary keys and any database-generated columns are automatically returned and mapped to the entity objects.
When database-generated values are not needed, setting AutoMapOutputDirection to false improves performance:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(devices, options =>
{
options.AutoMapOutputDirection = false;
});
With this option disabled, the DeviceId property remains unpopulated after insertion, reducing overhead and improving performance.
Entity Framework Extensions - BulkInsertOptimized Method
The BulkInsertOptimized method provides an alternative to BulkInsert with built-in performance analysis capabilities.
While BulkInsertOptimized behaves similarly to BulkInsert with AutoMapOutputDirection = false, it offers a key advantage: it returns a BulkOptimizedAnalysis object containing performance hints and optimization recommendations.
Here is how to insert 10,000 devices with BulkInsertOptimizedAsync:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(10_000);
await dbContext.BulkInsertOptimizedAsync(devices);
In this article, I explained in depth the performance difference between BulkInsertAsync and BulkInsertOptimizedAsync methods.
Entity Framework Extensions - IncludeGraph Option for Related Entities
The IncludeGraph option enables automatic insertion of related entities within an object graph. This feature is particularly useful when working with parent-child relationships, such as devices with their associated components.
Consider a scenario with 10 devices, each containing three components:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(10);
foreach (var device in devices)
{
device.Components = GenerateComponents(3);
}
The IncludeGraph option handles the insertion of the entire object graph, including all related entities:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(devices, options =>
{
options.IncludeGraph = true;
});
This option automatically inserts related entities from the graph of objects and preserves their relationships.
This is a very useful option, typically achieved with a single line of code in EF Core Extensions.
Imagine how much code you will write when using SQL Bulk Copy.
Entity Framework Extensions - AutoTruncate Option for String Length Management
The AutoTruncate option automatically truncates string values to match the maximum length defined in the Entity Framework mapping. When enabled, strings exceeding the database column length are trimmed before insertion, preventing length constraint violations.
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var devices = GenerateDevices(10);
foreach (var device in devices)
{
device.HardwareVersion += " some long text that needs to be truncated by EF Core Extensions library";
}
await dbContext.BulkInsertAsync(devices, options =>
{
options.AutoTruncate = true;
});
👉 Read original article on my newsletter: https://antondevtips.com/blog/entity-framework-extensions-options-explained
Top comments (0)