EF Core Bulk Insert Update - heartlanguage2024/heartlanguage2024 GitHub Wiki

Insert or update a large list of records (like 10,000 rows!) into a database using Entity Framework Core (EF Core).

1. 🧨 The Problem with the Old Way

The original method is very slow ⏳ when working with large amounts of data (like 10,000 records) because it:

  • Uses .Find() one-by-one
  • Checks the database for each single record
  • Triggers lots of database round-trips 🚐

2. 🧾 BEFORE: The Original Code (❌ Slow Version)

envSetupList.ForEach(item =>
{
    var itemId = idProperty.GetValue(item);

    var dbItem = dbContext.Set<T>().Local.FirstOrDefault(e =>
    {
        var entityId = idProperty.GetValue(e);
        return Equals(entityId, itemId);
    }) ?? dbContext.Set<T>().Find(itemId); // πŸ”΄ 1 DB call per item!

    if (dbItem != null)
    {
        dbContext.Entry(dbItem).CurrentValues.SetValues(item); // ✏️ Update
    }
    else
    {
        dbContext.Set<T>().Add(item); // βž• Add new
    }
});

3. 🧠 Step-by-Step Explanation of the Old Code

Line What It Does Emoji
itemId = idProperty.GetValue(item); Gets the ID from the current object πŸ†”
dbContext.Set<T>().Find(itemId); Looks up that ID in the database πŸ”πŸ“¦
if (dbItem != null) If found, it means it's an update πŸ“
else Otherwise it's a new record βž•

❗ But for every single item, it does this again and again. That’s 10,000 times if we have 10,000 records = very slow 😒


4. πŸš€ AFTER: The Improved Version (βœ… Fast Version)

// 1️⃣ Get all IDs from input list
var idsToCheck = envSetupList
    .Select(item => item.Id)
    .Where(id => id != null)
    .Distinct()
    .ToHashSet(); // βœ… Fast set lookup

// 2️⃣ Load all existing items from DB in one go!
var existingEntities = dbContext.Set<T>()
    .Where(e => idsToCheck.Contains(e.Id)) // βœ… Translatable LINQ!
    .ToDictionary(e => e.Id); // πŸ’Ό Convert to dictionary for fast access

// 3️⃣ Loop over input list just once
foreach (var item in envSetupList)
{
    if (item.Id != null && existingEntities.TryGetValue(item.Id, out var dbItem))
    {
        dbContext.Entry(dbItem).CurrentValues.SetValues(item); // ✏️ Update
    }
    else
    {
        dbContext.Set<T>().Add(item); // βž• Add new
    }
}

5. πŸ” Step-by-Step Explanation of the Improved Version

Line What It Does Emoji
.Select(item => item.Id) Grabs all the IDs in the list πŸ†”πŸ†”πŸ†”
.Distinct() Removes duplicates βŒπŸ“‹
.ToHashSet() Makes lookups very fast πŸš€
.Where(e => idsToCheck.Contains(e.Id)) Fetches all existing rows in 1 SQL query 🧠⚑
.ToDictionary(e => e.Id) So we can look up quickly during update πŸ“’
foreach (...) Loop through your list πŸ”
TryGetValue(...) Checks if this item is already in the DB πŸ€”βœ”
SetValues(item) Updates existing πŸ“
Add(item) Adds new if not found βž•

6. 🏁 Why the Improved Version is Faster

Feature Old Way Improved Way
DB Lookups 1 per item = 10,000 calls! Just 1 query to get all
Loop Type .ForEach on list foreach with dictionary lookup
Existence Check Find() (slow) TryGetValue() (super fast)

Imagine if you go to the library to pick 10,000 books.
πŸ“š

  • ❌ Old Way: You walk 10,000 times, one book each trip.
  • βœ… New Way: You give a list of books to the librarian and get all in one go! πŸ™Œ

7. πŸŽ“ Summary – When to Use Which

βœ… Use the Improved Version when:

  • You have thousands of records
  • You want bulk update performance
  • You want one DB call only

⚠️ The Old Version is okay if:

  • You have very small datasets (< 100)
  • You need per-record logic (logging, validation)

⚠️ Disabling Change Tracking in Entity Framework

Caution: The following code disables the change tracking feature in Entity Framework (EF), which may lead to issues with correctly tracking and triggering updates to dependent or calculated properties (such as SangiChohenText). This can be problematic, especially if the property relies on navigation property changes or value propagation.

// Disable change tracking for improved performance during bulk operations
dbContext.ChangeTracker.AutoDetectChangesEnabled = false;

Key Considerations:

  • πŸ› οΈ Performance vs. Accuracy: While disabling change tracking can enhance performance for bulk operations, it may result in EF failing to detect changes in related entities. This can cause issues where updates to properties, such as those calculated from navigation properties, are not propagated or reflected correctly.
  • ⚠️ Use with Caution: Only use this approach when you're certain that no changes to dependent properties are required during the operation. Re-enable change tracking afterward if further updates to your entities are necessary.