La forma más rápida de insertar en Entity Framework

Resuelto Bongo Sharp asked hace 13 años • 32 respuestas

Estoy buscando la forma más rápida de insertar en Entity Framework.

Pregunto esto por el escenario en el que tienes un activo TransactionScopey la inserción es enorme (4000+). Potencialmente, puede durar más de 10 minutos (tiempo de espera predeterminado de las transacciones) y esto dará lugar a una transacción incompleta.

Bongo Sharp avatar May 10 '11 00:05 Bongo Sharp
Aceptado

A su comentario en los comentarios a su pregunta:

"...GuardandoCambios ( para cada registro )..."

¡Eso es lo peor que puedes hacer! Llamar SaveChanges()a cada registro ralentiza extremadamente las inserciones masivas. Haría algunas pruebas sencillas que muy probablemente mejorarán el rendimiento:

  • Llame SaveChanges()una vez después de TODOS los registros.
  • Llame SaveChanges()después de, por ejemplo, 100 registros.
  • Llame SaveChanges()después, por ejemplo, de 100 registros, elimine el contexto y cree uno nuevo.
  • Deshabilitar la detección de cambios

Para inserciones masivas estoy trabajando y experimentando con un patrón como este:

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;            
        foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

private MyDbContext AddToContext(MyDbContext context,
    Entity entity, int count, int commitCount, bool recreateContext)
{
    context.Set<Entity>().Add(entity);

    if (count % commitCount == 0)
    {
        context.SaveChanges();
        if (recreateContext)
        {
            context.Dispose();
            context = new MyDbContext();
            context.Configuration.AutoDetectChangesEnabled = false;
        }
    }

    return context;
}

Tengo un programa de prueba que inserta 560.000 entidades (9 propiedades escalares, sin propiedades de navegación) en la base de datos. Con este código funciona en menos de 3 minutos.

Para el rendimiento es importante llamar SaveChanges()después de "muchos" registros ("muchos" alrededor de 100 o 1000). También mejora el rendimiento al eliminar el contexto después de SaveChanges y crear uno nuevo. Esto borra el contexto de todas las entidades, SaveChangesno hace eso, las entidades todavía están adjuntas al contexto en state Unchanged. Es el tamaño creciente de las entidades adheridas al contexto lo que frena paso a paso la inserción. Por lo tanto, es útil eliminarlo después de un tiempo.

Aquí hay algunas medidas para mis 560000 entidades:

  • commitCount = 1, recreateContext = false: muchas horas (Ese es su procedimiento actual)
  • commitCount = 100, recreateContext = false: más de 20 minutos
  • commitCount = 1000, recreateContext = false: 242 seg
  • commitCount = 10000, recreateContext = false: 202 seg
  • commitCount = 100000, recreateContext = false: 199 seg
  • commitCount = 1000000, recreateContext = false: excepción de falta de memoria
  • commitCount = 1, recreateContext = true: más de 10 minutos
  • commitCount = 10, recreateContext = verdadero: 241 seg
  • commitCount = 100, recreateContext = verdadero: 164 seg
  • commitCount = 1000, recreateContext = verdadero: 191 seg

El comportamiento en la primera prueba anterior es que el rendimiento es muy no lineal y disminuye extremadamente con el tiempo. ("Muchas horas" es una estimación, nunca terminé esta prueba, me detuve en 50.000 entidades después de 20 minutos). Este comportamiento no lineal no es tan significativo en todas las demás pruebas.

Slauma avatar May 09 '2011 20:05 Slauma

Esta combinación aumenta la velocidad bastante bien.

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
arkhivania avatar Aug 12 '2012 15:08 arkhivania

Como nunca se mencionó aquí, quiero recomendar EFCore.BulkExtensions aquí.

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);
Manfred Wippel avatar Feb 22 '2019 08:02 Manfred Wippel

Deberías considerar usar el System.Data.SqlClient.SqlBulkCopypara esto. Aquí está la documentación y, por supuesto, hay muchos tutoriales en línea.

Lo siento, sé que estabas buscando una respuesta simple para que EF haga lo que quieres, pero las operaciones masivas no son realmente para lo que sirven los ORM.

Adam Rackis avatar May 09 '2011 17:05 Adam Rackis

La forma más rápida sería utilizar la extensión de inserción masiva , que desarrollé

nota: este es un producto comercial, no gratuito

Utiliza SqlBulkCopy y un lector de datos personalizado para obtener el máximo rendimiento. Como resultado, es 20 veces más rápido que usar una inserción normal o AddRange. EntityFramework.BulkInsert y EF AddRange

el uso es extremadamente simple

context.BulkInsert(hugeAmountOfEntities);
 avatar Feb 17 '2014 21:02