Dans une de mes nouvelles missions, je devais importer un fort volume de données dans une base de donnée SQLServer.

Chaque donnée “importée” devait ou être mise à jour si elle existait déjà, ou être créée si elle n’existait pas.

La première solution qui m’est venue à l’idée était de faire trois requêtes par données importée :

  • la première en envoyant la liste d’Id que l’on veut importer (le serveur répondant ceux existant)

  • la seconde pour les inserts

  • la troisième pour les mises à jour

Or cette méthode a l’inconvénient de diviser l’import en 3 requêtes SQL différentes. Ce qui veut dire qu’on a la possibilité que le serveur SQL échoue à 3 endroits au lieu d’un et on a aussi 3 aller-retour avec ce serveur…

L’idéal est d’envoyer la liste complète des imports à la base en une seule requête SQL de données et que celle-ci les insère ou mette à jour directement.

SQLServer pouvant gérer des types complexes, on peut donc créer une procédure stockée permettant de faire le traitement en une seule fois.

Implémentation:

Pour ce faire il faut procéder en 3 étapes.

Il faut créer un TYPE SQLServer :

CREATE TYPE TalbetType AS TABLE 
( 
  Id uniqueidentifier NOT NULL,
  Name[nvarchar](MAX)NOT NULL,  
  ExternalId varchar(MAX) NOT NULL, 
  Type int NULL
);

Il faut créer la procédure stockée :

CREATE PROCEDURE [dbo].[SaveTablets]
(
    @Tablets [dbo].[TalbetType] READONLY
)
AS
BEGIN
    MERGE [dbo].[Tablets ] as target
    USING @Tablets AS source
    ON target.[ExternalId ] = source.[ExternalId]
    WHEN MATCHED THEN
    UPDATE SET target.[Id] = source.[Id],
    target.[ExternalId] = source.[ExternalId],
    target.[Name] = source.[Name],
    target.[Type] = source.[Type]
    WHEN NOT MATCHED THEN
    INSERT ([Id],[Name],[ExternalId],[Type])
    VALUES (source.[Id],source.[Name], source.[ExternalId], source.[Type]);
END

Et ensuite pour appeler la procédure stockée , il faut faire de la manière suivante :

            //Creation du datatable correspondant au type TabletType
            var dataTable = new DataTable("dt");
            dataTable.Columns.Add("Id", typeof(Guid));
            dataTable.Columns.Add("Name", typeof(string));
            dataTable.Columns.Add("ExternalId", typeof(string));
            dataTable.Columns.Add(new DataColumn("Type", typeof(int)) { AllowDBNull = true });        
            foreach (var tablet in tablets)
            {
                DataRow dataRow = dataTable.NewRow();
                dataRow["Id"] = tablet.Id;
                dataRow["Name"] = tablet.Name;
                dataRow["Email "] = tablet.Email;
                dataRow["ExternalId"] = tablet .ExternalId;
                if (tablet.Type.HasValue)
                    dataRow["Type"] = attendee.Type.Value;
                else
                    dataRow["Type"] = DBNull.Value;
                dataTable.Rows.Add(dataRow);
                dataRow.AcceptChanges();
            }
            //Création du parametre SQL
            var tableParameter = new SqlParameter("tablets", dataTable);
            tableParameter.TypeName = "TalbetType";
            tableParameter.SqlDbType = SqlDbType.Structured;
            //On execute la requête
            return await _context.Database.ExecuteSqlCommandAsync("SaveTablets @tablets", tableParameter);

Avantages:

Les avantages de cette méthode sont :

  • on reste dans un mode fortement typé les données qu’on envoie à SQLServer ont un typage fort donc pas besoin de parser dans la procédure SQL pour récupérer les infos.

  • tout l’import est réalisé en une seule requête SQL (donc un seul aller/retour).