
Entity Framework Extensions Options Explained: Everything You Can Customize
Bulk operations are a powerful feature of Entity Framework Extensions (EF Extensions). When dealing with large datasets — imports, migrations, synchronizations, background jobs — BulkInsert brings exceptional performance and flexibility. Its real strength comes from the many options you can customize to control behavior, column mapping, batching, and provider-specific behavior
In this article, let’s take one of the most customizable areas of EF Extensions — BulkInsert Options, and go deep into what they do, when to use them, and how they affect performance, behavior, and compatibility.
What Are BulkInsert Options in EF Extensions?
BulkInsert improves the performance of large insert operations by skipping EF Core’s change tracker and using optimized SQL operations.
However, real applications vary widely:
- Sometimes identity values must be preserved.
- Sometimes referential integrity matters.
- Sometimes constraints must be ignored temporarily.
- Sometimes triggers must fire.
BulkInsert options allow configuring all these scenarios without changing the database or application architecture.
Behavior Options
InsertIfNotExists
InsertIfNotExists option ensures only new records are inserted. EF Extensions checks existing data using primary keys or custom key mappings before performing the insert. It is helpful when processing repeated imports, device data, or logs that may contain duplicates.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.InsertIfNotExists = true;
}, ct);- Prevents duplicate data
- Useful when importing recurring files
- Supports custom key selection
InsertKeepIdentity
When you need to insert records with predefined identity values (IDs), this option preserves those values. It is especially important during data migrations or restoring snapshots
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.InsertKeepIdentity = true;
}, ct);- Maintains original identity values
- Important for historical or migrated data
- Handles SQL Server identity insert internally
InsertNotMatchedAndFormula / InsertPrimaryKeyAndFormula
These options allow applying additional filtering logic to decide whether a row qualifies for insertion. They are useful when importing from external systems or staging environments.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.InsertNotMatchedAndFormula = "Quantity > 0";
}, ct);using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.InsertPrimaryKeyAndFormula = "Status = 'Active'";
options.InsertIfNotExists = true;
}, ct);- Adds custom SQL conditions
- Supports more complex import logic
- Helpful in ETL pipelines
InsertStagingTableFilterFormula
When inserting from a staging table, this option filters which rows should be inserted into the final table.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.InsertStagingTableFilterFormula = "IsValid = 1";
}, ct);- Ideal for multi-step imports
- Avoids inserting temporary or invalid data
Data Preparation & Relationship Options
AutoTruncate
When dealing with user-generated or third-party data, strings may exceed database-defined lengths. AutoTruncate automatically trims those strings to fit the column definition before insertion — preventing failures and keeping the import smooth.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.AutoTruncate = true;
}, ct);- Prevents errors caused by long strings
- Useful for user-generated or uncontrolled data sources
ExplicitValueResolutionMode
This option controls how explicit values are handled when they normally wouldn’t be set manually — such as computed columns or default values. It ensures your application-defined values are respected when required.
- Preserves timestamps or system values when needed
- Avoids conflicts with database defaults
IncludeGraph
IncludeGraph inserts not only the root entities but also their related child entities. It is helpful for aggregate inserts where parent and children must be inserted together.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(orders, options =>
{
options.IncludeGraph = true;
}, ct);- Supports parent-child insert operations
- Maintains referential integrity
- Works well with domain-driven aggregates
IncludeGraphBuilder
For more complex graph scenarios, IncludeGraphBuilder gives you full control over how child entities should be inserted. It allows customizing ID propagation, relationship handling, and child configuration.
using Z.EntityFramework.Extensions;
await dbContext.BulkMergeAsync(customers, options =>
{
options.IncludeGraph = true;
options.IncludeGraphOperationBuilder = operation =>
{
if (operation is BulkOperation<Customer> customerOp)
{
customerOp.ColumnPrimaryKeyExpression = x => x.Email;
}
else if (operation is BulkOperation<Order> orderOp)
{
orderOp.IsReadOnly = true;
}
};
}, ct);- Fine-grained control on related entities
- Useful for customizing how child IDs or references are handled
Column & Property Options
ColumnInputExpression / ColumnInputNames
These options control which columns are included during the insert. Often useful when importing partial data or ignoring large/unnecessary fields.
- Reduces insert payload
- Improves performance for large datasets
ColumnPrimaryKeyExpression / ColumnPrimaryKeyNames
Instead of relying solely on the database’s primary key, BulkInsert can use custom key definitions to determine whether a record already exists. This becomes especially important when using InsertIfNotExists options, because EF Extensions needs a way to decide what counts as a duplicate.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(products, options =>
{
options.InsertIfNotExists = true;
options.ColumnPrimaryKeyExpression = x => new { x.Name };
}, ct);- Supports natural and business keys
- Helps avoid duplicates when primary keys are not suitable
IgnoreOnInsertExpression / IgnoreOnInsertNames
These options exclude specific properties from the insert. Ideal for fields computed by the database or populated later.
- Prevents overwriting computed values
- Keeps inserts clean and minimal
Performance Optimization Options
AutoMapOutputDirection
When you don’t need identity values or computed fields returned after the insert, turning this off greatly improves performance.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(orders, options =>
{
options.AutoMapOutputDirection = true;
}, ct);- Faster bulk operations
- Ideal for pure import tasks
BatchSize, BatchTimeout, BatchDelayInterval
These three options control how EF Extensions sends large amounts of data to the database. Instead of inserting everything in one massive operation, BulkInsert can split the workload into smaller, more manageable batches. This improves stability and prevents performance issues during big imports.
- BatchSize – determines how many records go into each batch.
- BatchTimeout – sets the maximum time a single batch is allowed to run.
- BatchDelayInterval – adds a small pause between batches.
using Z.EntityFramework.Extensions;
await dbContext.BulkInsertAsync(orders, options =>
{
options.BatchSize = 10_000;
options.BatchTimeout = 120;
options.BatchDelayInterval = 100;
}, ct);- Controls memory usage
- Prevents database saturation
- Helps process millions of records reliably
QueryHint / TableHint
Allows specifying SQL-level hints to optimize how the insert is executed.
- Can reduce locking overhead
- Useful in high-throughput systems
UseTableLock
UseTableLock tells Entity Framework Extensions to use a table-level lock instead of row-level locks during the bulk insert. Instead of locking individual rows one at a time (which creates a lot of lock fragments and overhead), SQL locks the entire table for the duration of the insert operation.
- Greatly improves insert speed
- Best used during low-concurrency hours
Provider-Specific Options
PostgreSQL Options
PostgreSQL includes several highly useful insert behaviors:
- UsePostgreSqlInsertOnConflictDoNothing – Silently skips rows that would cause a constraint violation
- UsePostgreSqlInsertOverridingSystemValue – allows incoming data to override system-generated values such as timestamps.
- UsePostgreSqlInsertOverridingUserValue – Overrides user-defined column defaults during the insert.
Oracle Options
Oracle insert and select hints allow controlling how the database optimizer treats the BulkInsert. This is helpful for improving speed in large Oracle datasets or tuning execution paths.
SqlBulkCopyOptions (SQL Server)
SQL Server-specific insert behavior can be customized when SqlBulkCopy is used internally. This includes controlling triggers, constraints, and batch-level rules.
- Allows triggers to fire when needed
- Keeps constraints consistent
- Supports specialized SQL Server workflows
General Utility Options
Audit
Enables tracking metadata for inserted records. Helpful for high-compliance industries and internal logging.
using Z.EntityFramework.Extensions;
// Define a list to capture audit data
List<AuditEntry> auditEntries = new();
await dbContext.BulkMergeAsync(orders, options =>
{
options.UseAudit = true;
options.AuditEntries = auditEntries;
}, ct);FutureAction
Allows queuing multiple operations and executing them together for better batching efficiency.
Log / LogDump
Provides visibility into generated SQL during bulk operations — extremely helpful during debugging.
using Z.EntityFramework.Extensions;
// Example 1: Write SQL logs directly to the console
await dbContext.BulkMergeAsync(customers, options =>
{
options.Log = Console.WriteLine;
}, ct);
// Example 2: Append all log messages to a StringBuilder
var sb = new StringBuilder();
await dbContext.BulkUpdateAsync(orders, options =>
{
options.Log = message => sb.AppendLine(message);
}, ct);
Console.WriteLine(sb.ToString());using Z.EntityFramework.Extensions;
// Collect logs for analysis after the operation completes
var logBuilder = new StringBuilder();
await dbContext.BulkInsertAsync(products, options =>
{
options.UseLogDump = true;
options.LogDump = logBuilder;
}, ct);
Console.WriteLine(logBuilder.ToString());RowsAffected
Gives exact numbers of inserted rows after the operation completes.
Summary
Entity Framework Extensions provides a broad set of options that allow developers to customize BulkInsert behavior exactly the way they need. From duplicate prevention and identity preservation to relationship handling, column selection, batch tuning, and provider-specific enhancements, EF Extensions gives full control over large-scale data operations. Whether you’re importing millions of records or updating complex aggregates, these options ensure reliable, predictable, and efficient bulk insert behavior.
Takeaways
- Behavior options help keep data clean and consistent.
- Column and property settings tailor exactly what gets inserted.
- Relationship options make it easy to insert aggregates.
- Batch and mapping settings significantly improve performance.
- Provider-specific features ensure smooth operation across major databases.
- Logging, auditing, and future actions provide better control and observability.
