# Import de données sur SQLServer

> 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 à…

- Auteur: Jérôme Giacomini
- Langue: fr
- URL canonique: [Import de données sur SQLServer](https://jeromegiacomini.net/fr/articles/import-de-donnee-de-masse-sur-sqlserver)
- Dernière modification: 2026-09-05
- Thèmes: C#, SQL

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 :

```sql
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 :

```sql
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 :

```csharp
            //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).
