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


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

Le pregunto esto por el escenario en el que tiene un TransactionScope activo y la inserción es enorme (4000+). Puede durar más de 10 minutos (tiempo de espera predeterminado de las transacciones), y esto dará lugar a una transacción incompleta.


543
2018-05-09 17:14


origen


Respuestas:


A su comentario en los comentarios a su pregunta:

"...Guardando cambios (para cada   grabar) ... "

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

  • Llamada SaveChanges() una vez después de TODOS los registros.
  • Llamada SaveChanges() después de, por ejemplo, 100 registros.
  • Llamada SaveChanges() después de, por ejemplo, 100 registros y descartar el contexto y crear uno nuevo.
  • Deshabilitar detección de cambio

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 para eliminar el contexto después de SaveChanges y crear uno nuevo. Esto borra el contexto de todas las entidades, SaveChanges no hace eso, las entidades todavía están unidas al contexto en el estado Unchanged. Es el tamaño creciente de las entidades adjuntas en el contexto lo que ralentiza la inserción paso a paso. Por lo tanto, es útil aclararlo después de un tiempo.

Aquí hay algunas medidas para mis 560,000 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 sec
  • commitCount = 10000, recreateContext = false: 202 sec
  • commitCount = 100000, recreateContext = false: 199 seg
  • commitCount = 1000000, recreateContext = false: excepción de memoria insuficiente
  • commitCount = 1, recreateContext = true: más de 10 minutos
  • commitCount = 10, recreateContext = true: 241 segundos
  • commitCount = 100, recreateContext = true: 164 sec
  • commitCount = 1000, recreateContext = true: 191 sec

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 otras pruebas.


841
2018-05-09 20:33



Esta combinación aumenta la velocidad lo suficientemente bien.

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

162
2017-08-12 15:52



La forma más rápida sería usar extensión de inserción a granelque desarrollé

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

el uso es extremadamente simple

context.BulkInsert(hugeAmountOfEntities);

96
2018-02-17 21:18



Deberías mirar usando el System.Data.SqlClient.SqlBulkCopy para esto. Aquí esta la documentacióny, por supuesto, hay muchos tutoriales en línea.

Lo siento, sé que estás buscando una respuesta simple para que EF haga lo que quieras, pero las operaciones masivas no son realmente para lo que están destinadas las ORM.


66
2018-05-09 17:17



Estoy de acuerdo con Adam Rackis. SqlBulkCopy es la forma más rápida de transferir registros masivos de una fuente de datos a otra. Utilicé esto para copiar 20K registros y tardó menos de 3 segundos. Echa un vistazo al ejemplo a continuación.

public static void InsertIntoMembers(DataTable dataTable)
{           
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Members";
                sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
                sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
                sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
                sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
                sqlBulkCopy.ColumnMappings.Add("Email", "Email");

                sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
                sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
                sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
                sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
                sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");

                sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
                sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");

                sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }

    }
}

46
2018-01-15 10:27



Investigué la respuesta de Slauma (que es impresionante, gracias por la idea), y reduje el tamaño del lote hasta que alcancé la velocidad óptima. Mirando los resultados de la Slauma:

  • commitCount = 1, recreateContext = true: más de 10 minutos
  • commitCount = 10, recreateContext = true: 241 sec
  • commitCount = 100, recreateContext = true: 164 sec
  • commitCount = 1000, recreateContext = true: 191 sec

Es visible que hay un aumento de velocidad al pasar de 1 a 10, y de 10 a 100, pero de 100 a 1000 la velocidad de inserción vuelve a caer.

Así que me he centrado en lo que sucede cuando reduces el tamaño del lote a un valor entre 10 y 100, y aquí están mis resultados (estoy usando diferentes contenidos de fila, así que mis tiempos son de diferente valor):

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

Según mis resultados, el óptimo real es alrededor del valor de 30 para el tamaño del lote. Es menos de 10 y 100. El problema es que no tengo idea de por qué es óptimo 30, ni podría haber encontrado ninguna explicación lógica para ello.


18
2018-03-26 08:46



Recomendaría este artículo sobre cómo hacer inserciones masivas usando EF.

Entity Framework y INSERTs lentos

Él explora estas áreas y compara el rendimiento:

  1. EF predeterminada (57 minutos para completar la adición de 30,000 registros)
  2. Reemplazar con el código ADO.NET (25 segundos para esos mismos 30,000)
  3. Bloqueo de contexto: mantenga pequeño el gráfico de contexto activo usando un nuevo contexto para cada unidad de trabajo (los mismos 30,000 insertos tardan 33 segundos)
  4. Grandes listas: desactiva AutoDetectChangesEnabled (reduce el tiempo a unos 20 segundos)
  5. Batching (hasta 16 segundos)
  6. DbTable.AddRange () - (el rendimiento está en el rango 12)

16
2018-06-20 03:10



Como otras personas han dicho que SqlBulkCopy es la forma de hacerlo si desea un buen rendimiento de inserción.

Es un poco engorroso implementarlo, pero hay bibliotecas que pueden ayudarlo. Hay algunos por ahí, pero voy a enchufar desvergonzadamente mi propia biblioteca esta vez: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

El único código que necesitarías es:

 using (var db = new YourDbContext())
 {
     EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
 }

Entonces, ¿cuánto más rápido es? Es muy difícil de decir porque depende de muchos factores, el rendimiento de la computadora, la red, el tamaño del objeto, etc. Las pruebas de rendimiento que he realizado sugieren que se pueden insertar 25k entidades alrededor de los 10 segundos. forma estándar en localhost SI optimiza su configuración de EF como se menciona en las otras respuestas. Con EFUtilities que toma alrededor de 300ms. Aún más interesante es que he ahorrado alrededor de 3 millones de entidades en menos de 15 segundos con este método, con un promedio de 200 mil entidades por segundo.

El único problema es, por supuesto, si necesita insertar datos relajados. Esto se puede hacer eficientemente en el servidor sql usando el método anterior, pero requiere que tengas una estrategia de generación de Id que te permita generar identificadores en el código de la aplicación para el padre para que puedas establecer las claves foráneas. Esto se puede hacer usando GUID o algo así como la generación de id. HiLo.


14
2017-12-19 10:44