Improve Entity Framework DetectChanges Performance

Problem

You track multiple entities in your context, and your application suffers from performance issues causing by Entity Framework DetectChanges method.

Learn – Why Entity Framework DetectChanges is slow

Example

using (var ctx = new CustomerContext())
{
foreach(var line in lines)
{
var customer = new Customer();
// …code…
// DetectChanges is invoked every time you call Add
ctx.Customers.Add(customer);
}
ctx.SaveChanges();
}

Stack Overflow – Related Questions

Solution

  • REDUCE the amount of entities in your context
  • REDUCE the number of DetectChanges
  • SET AutoDetectChanges to false

REDUCE the amount of entities in your context

When adding/modifying multiple entities, split your logic into multiple batches to have fewer records in the context.

Why

More tracking entities your context contains, slower the DetectChanges method is!

# Performance Comparisons

Batch Size 100 Entities 1,000 Entities 10,000 Entities
unlimited 15 ms 1,050 ms 105,000 ms
10 3 ms 40 ms 350 ms
100 15 ms 125 ms 1,200 ms
1,000 15 ms 1,050 ms 10,200 ms

*: SaveChanges time not included

**: Entity with two relations


How

1. CREATE batchSize variable

2. CALL SaveChanges before creating a new batch

3. CALL SaveChanges

4. Done!

# Example

// 1. CREATE batchSize variable
int batchSize = 1000;
var ctx = new CustomerContext();
for (int i = 0; i < lines.Count; i++)
{
// 2. CALL SaveChanges before creating a new batch
if (i != 0 && i%batchSize == 0)
{
ctx.SaveChanges();
ctx = new CustomerContext();
}
var customer = new Customer();
// …code…
ctx.Customers.Add(customer);
}
// 3. CALL SaveChanges
ctx.SaveChanges();
// 4. Done!

Reduce the number of DetectChanges

  • Use AddRange over Add

Why?

  • The Add method DetectChanges after every records added.
  • The AddRange method DetectChanges after all records is added.

# Performance Comparisons

Operations 100 Entities 1,000 Entities 10,000 Entities
Add 15 ms 1,050 ms 105,000 ms
AddRange 1 ms 10 ms 120 ms

*: SaveChanges time not included

**: Entity with two relations


How

1. CREATE a list

2. ADD entity to the list

3. USE AddRange with the list

4. SaveChanges

5. Done!

# Example

using (var ctx = new CustomerContext())
{
// 1. CREATE a list
List<Customer> customers = new List<Customer>();
foreach(var line in lines)
{
var customer = new Customer();
// …code…
// 2. ADD entity to the list
customers.Add(customer);
}
// 3. USE AddRange with the list
ctx.Customers.AddRange(customers);
// 4. SaveChanges
ctx.SaveChanges();
// 5. Done!
}

SET AutoDetectChanges to false

When adding multiple entities, if you cannot use AddRange, set Entity Framework AutoDetectChanges to false

Why

  • The Add method DetectChanges after every records added.

By disabling AutoDetectChanges, the DetectChanges method will only be invoked when you do it.

# Performance Comparisons

AutoDetectChanges 100 Entities 1,000 Entities 10,000 Entities
True (Default) 15 ms 1,050 ms 105,000 ms
False 1 ms 14 ms 180 ms

*: SaveChanges time not included

**: Entity with two relations


How

1. SET AutoDetectChangesEnabled = false

2. CALL DetectChanges before SaveChanges

3. SaveChanges

4. Done!

# Example

using (var ctx = new CustomerContext())
{
// 1. SET AutoDetectChangesEnabled = false
ctx.Configuration.AutoDetectChangesEnabled = false;
foreach(var line in lines)
{
var customer = new Customer();
// …code…
ctx.Customers.Add(customer);
}
// 2. CALL DetectChanges before SaveChanges
ctx.ChangeTracker.DetectChanges();
// 3. SaveChanges
ctx.SaveChanges();
// 4. Done!
}

Why Entity Framework DetectChanges is slow?

More entities your context own, slower the DetectChanges method become!

Entity Framework do a great job detect all changes for us but in exchange, we pay the high performance cost!

Let look at this simple example to check the time taken when adding the record 1, 10, 100, 10000

using (var ctx = new EF6.CustomerContext())
{
for(int i = 0; i < lines.Count; i++)
{
ctx.Customers.Add(customer);
}
}

Imagine this method called every time you add a new entities!

Record # Time to Add Total Time Spend
Record # 1 0 ms 0 ms
Record # 10 0 ms 0 ms
Record # 100 0.1 ms 11 ms
Record # 1,000 1 ms 1,02 ms
Record # 10,000 21 ms 105,000 ms
Record # 100,000 210ms 3h

*: SaveChanges time not included

**: Entity with two relations

OMG! 3h to add 100,000 records to a @*&# list? 210 ms to add a sinle records once we reach 100,000 records

, 210ms to add a simple record to a list, that’s crazy!

.. to add the 10,000 records, that’s insane! and we didn’t have saved them yet in the database.

That’s very common on Stack Overflow someone ask why his importation take over 10h. Only by using AddRange over Add, they normally save more than 2h.

# What look Entity Framework DetectChanges Code?

internal virtual void DetectChanges()
{
IList<EntityEntry> entityEntriesForDetectChanges = this.GetEntityEntriesForDetectChanges();
if ((entityEntriesForDetectChanges != null) && this.TransactionManager.BeginDetectChanges())
{
try
{
DetectChangesInNavigationProperties(entityEntriesForDetectChanges);
DetectChangesInScalarAndComplexProperties(entityEntriesForDetectChanges);
DetectChangesInForeignKeys(entityEntriesForDetectChanges);
this.DetectConflicts(entityEntriesForDetectChanges);
this.TransactionManager.BeginAlignChanges();
this.AlignChangesInRelationships(entityEntriesForDetectChanges);
}
finally
{
this.TransactionManager.EndAlignChanges();
this.TransactionManager.EndDetectChanges();
}
}
}

The code look very scary and for sure look to take a lot of time. Imagine, this code is called every time you add an entity to your context!