sqlbulkcopyoptions keepidentity from example ejemplo performance sqlbulkcopy dapper

performance - keepidentity - Las inserciones a granel tardan más de lo esperado usando Dapper



sqlbulkcopy example (5)

Después de leer este artículo , decidí echar un vistazo más de cerca a la forma en que estaba usando Dapper.

Corrí este código en una base de datos vacía

var members = new List<Member>(); for (int i = 0; i < 50000; i++) { members.Add(new Member() { Username = i.toString(), IsActive = true }); } using (var scope = new TransactionScope()) { connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members); scope.Complete(); }

tardó unos 20 segundos. Eso es 2500 inserciones / segundo. No está mal, pero tampoco es bueno considerando que el blog estaba logrando 45k inserciones / segundo. ¿Hay una manera más eficiente de hacer esto en Dapper?

Además, como nota al margen, ¡ejecutar este código a través del depurador de Visual Studio tomó más de 3 minutos! Pensé que el depurador lo retrasaría un poco, pero realmente me sorprendió ver eso.

ACTUALIZAR

Así que esto

using (var scope = new TransactionScope()) { connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members); scope.Complete(); }

y esto

connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members);

ambos tardaron 20 segundos.

¡Pero esto tomó 4 segundos!

SqlTransaction trans = connection.BeginTransaction(); connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members, transaction: trans); trans.Commit();


El uso del método Execute con una sola instrucción de inserción nunca hará una inserción masiva ni será eficiente. Incluso la respuesta aceptada con una Transaction no hace un Bulk Insert .

Si desea realizar una Bulk Insert , use SqlBulkCopy https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy

No encontrarás nada más rápido que esto.

Dapper Plus

Descargo de responsabilidad : soy el dueño del proyecto Dapper Plus

Este proyecto no es gratuito pero ofrece todas las operaciones a granel:

  • BulkInsert
  • Actualización masiva
  • BulkDelete
  • BulkMerge

(Usar bajo el capó SqlBulkCopy )

Y algunas opciones más, como la salida de valores de identidad:

// CONFIGURE & MAP entity DapperPlusManager.Entity<Order>() .Table("Orders") .Identity(x => x.ID); // CHAIN & SAVE entity connection.BulkInsert(orders) .AlsoInsert(order => order.Items); .Include(x => x.ThenMerge(order => order.Invoice) .AlsoMerge(invoice => invoice.Items)) .AlsoMerge(x => x.ShippingAddress);

Nuestra biblioteca soporta múltiples proveedores:

  • servidor SQL
  • Compacto de SQL
  • Oráculo
  • MySql
  • PostgreSQL
  • SQLite
  • Firebird

Encontré todos estos ejemplos incompletos.

Aquí hay un código que cierra correctamente la conexión después de su uso, y también usa correctamente el transaccional para mejorar el rendimiento de ejecución, basado en las respuestas más recientes y mejores de este hilo.

using (var scope = new TransactionScope()) { Connection.Open(); Connection.Execute(sqlQuery, parameters); scope.Complete(); }


Lo mejor que pude lograr fue 50k registros en 4 segundos usando este enfoque

SqlTransaction trans = connection.BeginTransaction(); connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members, transaction: trans); trans.Commit();


Me encontré con esto recientemente y me di cuenta de que el TransactionScope se crea después de que se abre la conexión (asumo esto ya que Dappers Execute no abre la conexión, a diferencia de Query). De acuerdo con la respuesta P4 aquí: https://.com/a/2886326/455904 que no resultará en que la conexión sea manejada por el TransactionScope. Mi compañero de trabajo hizo algunas pruebas rápidas, y al abrir la conexión fuera del TransactionScope se redujo drásticamente el rendimiento.

Así que cambiar a lo siguiente debería funcionar:

// Assuming the connection isn''t already open using (var scope = new TransactionScope()) { connection.Open(); connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members); scope.Complete(); }


la variante más rápida para mí:

var dynamicParameters = new DynamicParameters(); var selects = new List<string>(); for (var i = 0; i < members.Length; i++) { var member = members[i]; var pUsername = $"u{i}"; var pIsActive = $"a{i}"; dynamicParameters.Add(pUsername, member.Username); dynamicParameters.Add(pIsActive, member.IsActive); selects.Add("select @{pUsername},@{pIsActive}"); } con.Execute($"insert into Member(Username, IsActive){string.Join(" union all ", selects)}", dynamicParameters);

que generan sql como:

INSERT TABLENAME (Column1,Column2,...) SELECT @u0,@a0... UNION ALL SELECT @u1,@a1... UNION ALL SELECT @u2,@a2...

esta consulta funciona más rápido porque sql agrega un conjunto de filas en lugar de agregar una fila a la vez. El cuello de botella no es escribir los datos, está escribiendo lo que está haciendo en el registro.

Además, observe las reglas de las transacciones mínimamente registradas.