web-dev-qa-db-fra.com

meilleure façon de faire des insertions en vrac en utilisant dapper.net

J'utilise le code suivant pour insérer des enregistrements dans une table dans SQL Server 2014

using (SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["myConnString"]))
{

   conn.Execute("INSERT statement here", insertList);

}

insertList est une liste qui contient 1 million d'articles. J'ai testé cet insert sur un bureau i5 et il a fallu environ 65 minutes pour insérer un million d'enregistrements dans SQL Server sur la même machine. Je ne sais pas comment Dapper fait les insertions dans les coulisses. Je ne veux certainement pas ouvrir et fermer la connexion à la base de données un million de fois!

Est-ce la meilleure façon de faire des insertions en masse dans Dapper ou dois-je essayer autre chose ou aller avec ADO.Net en utilisant la bibliothèque Enterprise?

MODIFIER

Avec le recul, je sais que l'utilisation d'ADO.Net sera meilleure, ainsi reformulera ma question. Je voudrais toujours savoir si c'est le mieux que Dapper puisse faire ou est-ce que je manque une meilleure façon de le faire dans Dapper lui-même?

11
user20358

Si vous recherchez les performances, je vous recommande d'utiliser SqlBulkCopy plutôt que d'insérer à l'aide de Dapper. Voir ici pour quelques comparaisons de performances: http://www.ikriv.com/dev/db/SqlInsert/SqlInsert.html

14
Alex Marshall

En s'appuyant sur le commentaire d'Ehsan Sajjad, l'une des manières consiste à écrire une procédure stockée qui a un paramètre READONLY de type de table défini par l'utilisateur .

Supposons que vous souhaitiez insérer en masse des contacts composés d'un prénom et d'un nom, voici comment procéder: 1) Créez un type de tableau:

CREATE TYPE [dbo].[MyTableType] AS TABLE(
    [FirstName] [varchar](50) NULL,
    [LastName] [varchar](50) NULL
)
GO

2) Créez maintenant un proc stocké qui utilise le type de table ci-dessus:

CREATE PROC [dbo].[YourProc]
/*other params here*/
@Names AS MyTableType READONLY
AS
/* proc body here 
 */
GO

3) Côté .NET, passez le paramètre en tant que System.Data.SqlDbType.Structured Cela implique généralement la création d'une table de données en mémoire, puis l'ajout de lignes et l'utilisation de cet objet DataTable comme paramètre @Names. REMARQUE: le DataTable est considéré comme gourmand en mémoire. Soyez prudent et profilez votre code pour vous assurer qu'il ne cause pas de problèmes de ressources sur votre serveur.

SOLUTION ALTENATIVE Utilisez l'approche décrite ici: https://stackoverflow.com/a/9947259/190476 La solution est pour SUPPRIMER mais peut également être adapté pour une insertion ou une mise à jour.

7
Sudhanshu Mishra

Le meilleur moyen gratuit d'insérer avec d'excellentes performances est d'utiliser la classe SqlBulkCopy directement comme l'ont suggéré Alex et Andreas.

Avertissement : Je suis le propriétaire du projet Dapper Plus

Ce projet n'est pas gratuit mais prend en charge les opérations suivantes:

  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge

En utilisant le mappage et en permettant de générer des valeurs comme des colonnes d'identité.

// 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);   
3
Jonathan Magnan

Le premier choix devrait être la copie en bloc SQL , car elle est à l'abri de l'injection SQL.

Cependant, il existe un moyen d'améliorer considérablement les performances. Vous pouvez fusionner plusieurs insertions en un seul SQL et avoir un seul appel au lieu de plusieurs. Donc au lieu de ça:

enter image description here

Vous pouvez avoir ceci:

enter image description here

Le code pour insérer des utilisateurs en masse peut ressembler à ceci:

public async Task InsertInBulk(IList<string> userNames)
{
    var sqls = GetSqlsInBatches(userNames);
    using (var connection = new SqlConnection(ConnectionString))
    {
        foreach (var sql in sqls)
        {
            await connection.ExecuteAsync(sql);
        }
    }
}

private IList<string> GetSqlsInBatches(IList<string> userNames)
{
    var insertSql = "INSERT INTO [Users] (Name, LastUpdatedAt) VALUES ";
    var valuesSql = "('{0}', getdate())";
    var batchSize = 1000;

    var sqlsToExecute = new List<string>();
    var numberOfBatches = (int)Math.Ceiling((double)userNames.Count / batchSize);

    for (int i = 0; i < numberOfBatches; i++)
    {
        var userToInsert = userNames.Skip(i * batchSize).Take(batchSize);
        var valuesToInsert = userToInsert.Select(u => string.Format(valuesSql, u));
        sqlsToExecute.Add(insertSql + string.Join(',', valuesToInsert));
    }

    return sqlsToExecute;
}

L'article complet et la comparaison des performances sont disponibles ici: http://www.michalbialecki.com/2019/05/21/bulk-insert-in-dapper/

0
Mik