La forma más rápida de insertar en Entity Framework
Estoy buscando la forma más rápida de insertar en Entity Framework.
Pregunto esto por el escenario en el que tienes un activo TransactionScope
y 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.
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, SaveChanges
no 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.
Esta combinación aumenta la velocidad bastante bien.
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
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);
Deberías considerar usar el System.Data.SqlClient.SqlBulkCopy
para 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.
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.
el uso es extremadamente simple
context.BulkInsert(hugeAmountOfEntities);