EF Core lacks a native method for mirroring a list to a database table. The hand-rolled diff-and-apply pattern (load, classify, insert/update/delete, SaveChanges) works but loads all rows into the change tracker, scales poorly, and is error-prone. Entity Framework Extensions (EFE) provides BulkSynchronize, which stages the source list in a temp table and runs a server-side MERGE to insert new rows, update changed rows, and delete rows absent from the source — all in one transaction without materializing existing rows in .NET memory. The key configuration option is ColumnSynchronizeDeleteKeySubsetExpression, which scopes the delete branch to a specific slice of the table (e.g., by SupplierId or TenantId) to avoid accidentally removing unrelated rows. Benchmarks show BulkSynchronize is 4–5x faster than the hand-rolled approach at 100K rows. The post covers four practical scenarios (API cache sync, reporting table refresh, reference table mirror, per-tenant sync), important production gotchas (change tracker staleness, FK constraints, interceptors not firing), and an honest assessment of when the paid EFE license is justified.

16m read timeFrom woodruff.dev
Post cover image
Table of contents
The Function Everyone Has Written, Nobody LovesThe Operation EF Core Won’t Give YouBulkSynchronize: One Call, Three OperationsScoping the Delete: The Option Worth KnowingFour Scenarios Where BulkSynchronize Earns Its PlaceThe Options You Will Actually UseThe NumbersWhen to Reach For BulkSynchronize and When Not ToProduction Notes Worth KnowingIs It Worth the License?Closing
1.3K Impressions