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 ExtensionsBulkInsert 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.

C#
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

C#
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.

C#
using Z.EntityFramework.Extensions;

await dbContext.BulkInsertAsync(products, options =>
{
     options.InsertNotMatchedAndFormula = "Quantity > 0";     
}, ct);
C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.
C#
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.

C#
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.

C#
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());
C#
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.

Found this article useful? Share it with your network and spark a conversation.