Bulk Insert in EF Core / EF6 (original) (raw)

Bulk InsertBoost your EF Core insert performance now

The BulkInsert method is the easiest way you can insert thousands of entities in EF Core. In addition to being super fast, you can also customize it with various options to insert entities the way you want—such as keeping identity values, inserting only entities that don't already exist in the database, and much more.

context.BulkInsert(customers);

context.BulkInsert(invoices, options => options.IncludeGraph = true);

Online Example (EF Core) | Online Example (EF6)

If you want to insert entities in EF Core even faster, you can use the BulkInsertOptimized method. The major difference between the two methods is:

🔑 Key Benefits

One of the main reasons people use our Bulk Insert with Entity Framework is for its performance and reduced memory usage. Another key reason is its flexibility, with hundreds of supported options — which we’ll explore later.

🔍 What is supported?

Our library supports all the common scenarios — and almost everything you can do with Entity Framework!

🚀 Performance Comparison

A very popular search on Google is **"Fastest way to Bulk Insert in EF Core"**—and that’s exactly what our library delivers!

Don't just take our word for it or blindly trust what we say. Instead, try it yourself using our online benchmark and see the results with a single click!

EF Core vs EFE

The SaveChanges method in EF Core is much faster than it was back in the EF6 days when inserting data. Why the improvement? The EF Core team introduced a new approach using a MERGE statement with a RETURNING clause—similar to the one we’ve been using for SQL Server since 2014. So yes, we were already doing something right!

Even with this new strategy, our library is still faster. That’s because we use SqlBulkCopy and have deeply optimized how data is handled behind the scenes. In fact, the performance gap becomes even more noticeable when your entities have more than just a few properties. If your entity only has 2 or 3 properties—like a very simple Customer with just Id, Name, and Email—the difference might seem smaller. But once you deal with real-world entities containing 10, 15, or more properties, our optimized approach truly shines.

Operation 1,000 Entities 2,000 Entities 5,000 Entities
SaveChanges 325 ms 575 ms 1,400 ms
BulkInsert (Outputting values) 60 ms 90 ms 150 ms
BulkInsert (Not Outputting values) 30 ms 50 ms 90 ms
BulkInsertOptimized 30 ms 50 ms 90 ms

👉 Try our Online Benchmark

In other words, to save 5,000 entities:

Our library provides the best performance when no data needs to be returned/outputted. That’s why we introduced the AutoMapOutputDirection = false option and the BulkInsertOptimized method.

EF Core vs EFE + Include Graph

Another important benchmark for EF Core is when inserting data that includes a graph of related entities. Being faster is one big advantage we offer—but just as important, our library uses only a fraction of the memory.

For example, when working with millions of entities, EF Core might use up to 2,000 MB, while our library needs only around 400 MB. That’s a huge difference, especially in memory-constrained environments.

In this benchmark, each Order entity includes 5 OrderItems. We use IncludeGraph to automatically handle related entities during the insert—so you don’t need to manually insert children or worry about setting their foreign keys.

Operation 1,000 Entities 2,000 Entities 5,000 Entities
SaveChanges 1,475 ms 2,600 ms 6,500 ms
BulkInsert + IncludeGraph 260 ms 450 ms 900 ms
BulkInsertOptimized + IncludeGraph 200 ms 350 ms 800 ms

👉 Try our Online Benchmark

⚠️ On .NET Fiddle, you won’t be able to run SaveChanges with more than around 1,800 entities before it crashes due to memory limits. But if you comment out the SaveChanges call, you’ll see that our library handles 5,000 entities just fine. This helps prove an important point: performance isn’t only about speed—memory usage matters too.

EF6 vs EFE

In EF6, the SaveChanges method makes one database round-trip for every entity it needs to insert. If you have hundreds or thousands of entities, our library is a must-have for this version. Otherwise, you're making your users wait forever for the save to complete.

Operation 1,000 Entities 2,000 Entities 5,000 Entities
SaveChanges 1,000 ms 2,000 ms 5,000 ms
BulkInsert (Outputting values) 90 ms 115 ms 150 ms
BulkInsert (Not Outputting values) 30 ms 35 ms 60 ms

👉 Try our Online Benchmark

In other words, to save 5,000 entities:

Bulk Insert Options

Configuring Options

We already saw in previous articles how to pass options to the BulkInsert method — but here’s a quick recap:

context.BulkInsert(list, options => options.InsertKeepIdentity = true);

context.BulkInsert(list, options => { options.InsertKeepIdentity = true; options.ColumnPrimaryKeyExpression = x => new { x.ID }; });

var options = context.CreateBulkOptions(); options.InsertKeepIdentity = true; options.ColumnPrimaryKeyExpression = x => new { x.ID };

context.BulkInsert(list, options);

💡 Tip: Using a BulkOperationOption instance is useful when you want to reuse the same configuration across multiple operations or keep your setup code more organized.

Common Options

Troubleshooting

Explicit Value Not Inserted

A major difference between BulkInsert and SaveChanges is how explicit values are handled.

In EF6, explicit values were always ignored—even if you specified a value for a column with a default value. However, EF Core changed this behavior and now automatically inserts the value you specify.

By default, our library still follows the same logic we used for EF6. But don’t worry—you can get the same behavior as EF Core by using the ExplicitValueResolutionMode option.

Here’s how you can do it:

context.BulkInsert(customers, options => { options.ExplicitValueResolutionMode = Z.EntityFramework.Extensions.ExplicitValueResolutionMode.SmartDefaultValueOnBulkInsert; });

Limitations

Hidden Navigation (EF6 only)

The BulkInsert method doesn’t rely on the ChangeTracker by default to maximize performance — unless there’s no other option.

For example, let’s say you want to insert a list of InvoiceItem, but there’s no direct navigation property or relation set toward the parent Invoice. In this case, you’ll need to add the parent entities to the ChangeTracker. This helps EF find and link the related Invoice for each InvoiceItem.

try { context.BulkInsert(items); } catch { Console.WriteLine("An error is thrown because the Invoice relation cannot be found."); }

context.Invoices.AddRange(invoices);

context.BulkInsert(items);

Online Example

Conclusion

The BulkInsert method is one of our most popular features. As we showed in this article, it doesn’t just offer better performance than SaveChanges — it also gives you access to many options that let you insert data exactly the way you want.


Last updated: 2025-04-06
Author: