28

We have a generic update method in our code that does a

foreach (var entity in entityList)
{
    Context.GetIDbSet<T>().Attach(entity);
    Context.SetState(entity, EntityState.Modified);
}

I'm testing this out by passing in an enumeration of entities and calling this once per entity.

What I'm finding is that an enumeration of 1000 entities takes approximately 47s to run. Is that expected behavior? Or is there something wrong with the code snippet?

Profiling showed the Attach() method was slower than the SetState() method.

The test I ran it on was on an entity with 50 properties and no relations if that has any impact.

taylonr
  • 10,732
  • 5
  • 37
  • 66

1 Answers1

71

I can confirm this slow behaviour and I also found the main reason. I've made a little test with the following model ...

public class MyClass
{
    public int Id { get; set; }
    public string P1 { get; set; }
    // ... properties P2 to P49, all of type string
    public string P50 { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<MyClass> MyClassSet { get; set; }
}

... and this test program ...

using (var context = new MyContext())
{
    var list = new List<MyClass>();
    for (int i = 0; i < 1000; i++)
    {
        var m = new MyClass()
        {
            Id = i+1,
            P1 = "Some text ....................................",
            // ... initialize P2 to P49, all with the same text
            P50 = "Some text ...................................."
        }
        list.Add(m);
    }

    Stopwatch watch = new Stopwatch();
    watch.Start();
    foreach (var entity in list)
    {
        context.Set<MyClass>().Attach(entity);
        context.Entry(entity).State = System.Data.EntityState.Modified;
    }
    watch.Stop();
    long time = watch.ElapsedMilliseconds;
}

Test 1

Exactly the code above:

--> time = 29,2 sec


Test 2

Comment out the line ...

//context.Entry(entity).State = System.Data.EntityState.Modified;

--> time = 15,3 sec


Test 3

Comment out the line ...

//context.Set<MyClass>().Attach(entity);

--> time = 57,3 sec

This result is very strange because I expected that calling Attach is not necessary because changing the state attaches anyway.


Test 4

Remove properties P6 to P50 (so we only have 5 strings in the entity), original code:

--> time = 3,4 sec

So, yes, obviously the number of properties strongly matters.


Test 5

Add the following line before the loop (model again with all 50 properties):

context.Configuration.AutoDetectChangesEnabled = false;

--> time = 1,4 sec


Test 6

Again with AutoDetectChangesEnabled = false but with only 5 properties:

--> time = 1,3 sec

So, without change tracking the number of properties doesn't matter so much anymore.


Conclusion

By far most of the time seems to be spent for taking the snapshot of the attached object's properties by the change tracking mechanism. If you don't need it disable change tracking for your code snippet. (I guess in your code your really don't need change tracking because by setting the entitiy's state to Modified you basically mark all properties as changed anyway. So all columns get sent to the database in an update statement.)

Edit

The test times above are in Debug mode. But Release mode doesn't make a big difference (for instance: Test 1 = 28,7 sec, Test 5 = 0,9 sec).

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • 2
    +1 This is awesome explanation and it makes sense because snapshot change tracking is considered as terribly slow but I thought that slow operation is actually evaluating changes but this looks like creating the snapshot is slow as well. Another gotcha in EF. – Ladislav Mrnka May 07 '11 at 13:51
  • This is an awesome answer... I had thought of change tracking but didn't know of a way to turn it off...I will try this out later today...thank you – taylonr May 07 '11 at 14:23
  • That did it. Thank you so much. This error was brought to my attention @ about 4pm yesterday and it kinda ruined my night thinking about it. Like I said, I was thinking something with tracking, but didn't think about a context setting, and might not have gotten there. – taylonr May 07 '11 at 15:47
  • 1
    In this thread (http://social.msdn.microsoft.com/Forums/en/adonetefx/thread/d607377e-bf68-495b-98b2-8ed5aa33d016) someone did a similar measurement for bulk **adding** entities to the DbContext - with similar results: `AutoDetectChangesEnabled` has a huge effect on the performance. – Slauma May 07 '11 at 17:03
  • @Slauma You're awesome. This one was really biting us; we figured it had something to do with change tracking but your wonderful answer really sealed the deal! – Pandincus Mar 20 '12 at 18:14
  • Wonderfull solution brilliant. – Umar Abbas Oct 23 '14 at 21:53
  • I had to update about 100K of entities. Using regular Attach it took me about 3 days (!!!). After I set "AutoDetectChangesEnabled = false" I made the same changes during 4 seconds... I wish I saw this answer 3 days earlier... Sad – Illidan Feb 06 '17 at 06:37