Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EntityFramework is painfully slow at executing an update query

We're investigating a performance issue where EF 6.1.3 is being painfully slow, and we cannot figure out what might be causing it.

The database context is initialized with:

Configuration.ProxyCreationEnabled = false;
Configuration.AutoDetectChangesEnabled = false;
Configuration.ValidateOnSaveEnabled = false;

We have isolated the performance issue to the following method:

protected virtual async Task<long> UpdateEntityInStoreAsync(T entity,
                                                            string[] changedProperties)
{
    using (var session = sessionFactory.CreateReadWriteSession(false, false))
    {
        var writer = session.Writer<T>();
        writer.Attach(entity);
        await writer.UpdatePropertyAsync(entity, changedProperties.ToArray()).ConfigureAwait(false);
    }
    return entity.Id;
}

There are two names in the changedProperties list, and EF correctly generated an update statement that updates just these two properties.

This method is called repeatedly (to process a collection of data items) and takes about 15-20 seconds to complete.

If we replace the method above with the following, execution time drops to 3-4 seconds:

protected virtual async Task<long> UpdateEntityInStoreAsync(T entity,
                                                            string[] changedProperties)
{
    var sql = $"update {entity.TypeName()}s set";
    var separator = false;
    foreach (var property in changedProperties)
    {
         sql += (separator ? ", " : " ") + property + " = @" + property;
         separator = true;
    }
    sql += " where id = @Id";
    var parameters = (from parameter in changedProperties.Concat(new[] { "Id" })
                      let property = entity.GetProperty(parameter)
                      select ContextManager.CreateSqlParameter(parameter, property.GetValue(entity))).ToArray();
    using (var session = sessionFactory.CreateReadWriteSession(false, false))
    {
        await session.UnderlyingDatabase.ExecuteSqlCommandAsync(sql, parameters).ConfigureAwait(false);
    }
    return entity.Id;
}

The UpdatePropertiesAsync method called on the writer (a repository implementation) looks like this:

public virtual async Task UpdatePropertyAsync(T entity, string[] changedPropertyNames, bool save = true)
{
    if (changedPropertyNames == null || changedPropertyNames.Length == 0)
    {
        return;
    }

    Array.ForEach(changedPropertyNames, name => context.Entry(entity).Property(name).IsModified = true);
    if (save)
        await context.SaveChangesAsync().ConfigureAwait(false);
    }
}

What is EF doing that completely kills performance? And is there anything we can do to work around it (short of using another ORM)?

like image 557
Morten Mertner Avatar asked Nov 23 '15 14:11

Morten Mertner


People also ask

Is EF core faster than EF6?

Entity Framework (EF) Core, Microsoft's object-to-database mapper library for . NET Framework, brings performance improvements for data updates in version 7, Microsoft claims. The performance of SaveChanges method in EF7 is up to 74% faster than in EF6, in some scenarios.

Is EF core faster than ado net?

It is because, in Entity Framework, the entity first translates the LINQ queries to SQL and then it processes the query to perform database operations. Also, the Entity Framework consists of a wrapper class for the ADO.Net. Due to this wrapper, the developer can perform the coding much faster in Entity Framework.

Is Entity Framework slow?

Entity Framework is a great tool, but in some cases its performance is slow. One such case arises when complex queries use “Contains”.


1 Answers

By timing the code I was able to see that the additional time spent by EF was in the call to Attach the object to the context, and not in the actual query to update the database.

By eliminating all object references (setting them to null before attaching the object and restoring them after the update is complete) the EF code runs in "comparable times" (5 seconds, but with lots of logging code) to the hand-written solution.

So it looks like EF has a "bug" (some might call it a feature) causing it to inspect the attached object recursively even though change tracking and validation have been disabled.

Update: EF 7 appears to have addressed this issue by allowing you to pass in a GraphBehavior enum when calling Attach.

like image 98
Morten Mertner Avatar answered Sep 21 '22 13:09

Morten Mertner