Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework - "Attach()" is slow

I'm using EF5 and attaching a disconnected graph of POCO entities to my context, something like this:-

using (var context = new MyEntities())
{
    context.Configuration.AutoDetectChangesEnabled = false;

    context.MyEntities.Attach(myEntity);

    // Code to walk the entity graph and set each entity's state
    // using ObjectStateManager omitted for clarity ..

    context.SaveChanges();
}

The entity "myEntity" is a large graph of entities, with many child collections, which in turn have their own child collections, and so on. The entire graph contains in the order of 10000 entities, but only a small number are usually changed.

The code to set the entity states and the actual SaveChanges() is fairly quick (<200ms). It's the Attach() that's the problem here, and takes 2.5 seconds, so I was wondering if this could be improved. I've seen articles that tell you to set AutoDetectChangesEnabled = false, which I'm doing above, but it makes no difference in my scenario. Why is this?

like image 616
Andrew Stephens Avatar asked Apr 30 '13 15:04

Andrew Stephens


1 Answers

I am afraid that 2,5 sec for attaching an object graph with 10000 entities is "normal". It's probably the entity snapshot creation that takes place when you attach the graph that takes this time.

If "only a small number are usually changed" - say 100 - you could consider to load the original entities from the database and change their properties instead of attaching the whole graph, for example:

using (var context = new MyEntities())
{
    // try with and without this line
    // context.Configuration.AutoDetectChangesEnabled = false;

    foreach (var child in myEntity.Children)
    {
        if (child.IsModified)
        {
            var childInDb = context.Children.Find(child.Id);
            context.Entry(childInDb).CurrentValues.SetValues(child);
        }
        //... etc.
    }
    //... etc.

    context.SaveChanges();
}

Although this will create a lot of single database queries, only "flat" entities without navigation properties will be loaded and attaching (that occurs when calling Find) won't consume much time. To reduce the number of queries you could also try to load entities of the same type as a "batch" using a Contains query:

    var modifiedChildIds = myEntity.Children
        .Where(c => c.IsModified).Select(c => c.Id);

    // one DB query
    context.Children.Where(c => modifiedChildIds.Contains(c.Id)).Load();

    foreach (var child in myEntity.Children)
    {
        if (child.IsModified)
        {
            // no DB query because the children are already loaded
            var childInDb = context.Children.Find(child.Id);
            context.Entry(childInDb).CurrentValues.SetValues(child);
        }
    }

It's just a simplified example under the assumption that you only have to change scalar properties of the entities. It can become arbitrarily more complex if modifications of relationships (children have been added to and/or deleted from the collections, etc.) are involved.

like image 166
Slauma Avatar answered Nov 07 '22 13:11

Slauma