web-dev-qa-db-fra.com

Mise à jour en vrac en C #

Pour insérer une énorme quantité de données dans une base de données, je collectais toutes les informations d'insertion dans une liste et convertissions cette liste en un fichier DataTable. J'insère ensuite cette liste dans une base de données via SqlBulkCopy.

Où j'envoie ma liste générée
LiMyList
qui contiennent des informations sur toutes les données en bloc que je souhaite insérer dans la base de données
et passez-le à mon opération d'insertion en bloc

InsertData(LiMyList, "MyTable");

InsertData est

 public static void InsertData<T>(List<T> list,string TableName)
        {
                DataTable dt = new DataTable("MyTable");
                clsBulkOperation blk = new clsBulkOperation();
                dt = ConvertToDataTable(list);
                ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
                using (SqlBulkCopy bulkcopy = new SqlBulkCopy(ConfigurationManager.ConnectionStrings["SchoolSoulDataEntitiesForReport"].ConnectionString))
                {
                    bulkcopy.BulkCopyTimeout = 660;
                    bulkcopy.DestinationTableName = TableName;
                    bulkcopy.WriteToServer(dt);
                }
        }    

public static DataTable ConvertToDataTable<T>(IList<T> data)
        {
            PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
            DataTable table = new DataTable();
            foreach (PropertyDescriptor prop in properties)
                table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
            foreach (T item in data)
            {
                DataRow row = table.NewRow();
                foreach (PropertyDescriptor prop in properties)
                    row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                table.Rows.Add(row);
            }
            return table;
        }

Maintenant, je veux effectuer une opération de mise à jour. Existe-t-il un moyen d’insérer des données dans SqlBulkCopy pour mettre à jour des données dans la base de données De C # .Net

55
Co. Aden

Ce que j'ai déjà fait consiste à effectuer une insertion en bloc des données dans une table temporaire, puis à utiliser une commande ou une procédure stockée pour mettre à jour les données associant la table temporaire à la table de destination. La table temporaire est une étape supplémentaire, mais vous pouvez obtenir un gain de performance avec l'insertion en bloc et une mise à jour massive si le nombre de lignes est important, par rapport à la mise à jour des données ligne par ligne.

Exemple:

public static void UpdateData<T>(List<T> list,string TableName)
{
    DataTable dt = new DataTable("MyTable");
    dt = ConvertToDataTable(list);

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SchoolSoulDataEntitiesForReport"].ConnectionString))
    {
        using (SqlCommand command = new SqlCommand("", conn))
        {
            try
            {
                conn.Open();

                //Creating temp table on database
                command.CommandText = "CREATE TABLE #TmpTable(...)";
                command.ExecuteNonQuery();

                //Bulk insert into temp table
                using (SqlBulkCopy bulkcopy = new SqlBulkCopy(conn))
                {
                    bulkcopy.BulkCopyTimeout = 660;
                    bulkcopy.DestinationTableName = "#TmpTable";
                    bulkcopy.WriteToServer(dt);
                    bulkcopy.Close();
                }

                // Updating destination table, and dropping temp table
                command.CommandTimeout = 300;
                command.CommandText = "UPDATE T SET ... FROM " + TableName + " T INNER JOIN #TmpTable Temp ON ...; DROP TABLE #TmpTable;";
                command.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                // Handle exception properly
            }
            finally
            {
                conn.Close();
            }
        }
    }
}

Notez qu'une seule connexion est utilisée pour effectuer toute l'opération afin de pouvoir utiliser la table temp à chaque étape, car la portée de la table temp est par connexion.

66

D'après mon expérience personnelle, le meilleur moyen de gérer cette situation consiste à utiliser une procédure stockée avec un Table-Valued Parameter et un User-Defined Table Type. Configurez simplement le type avec les colonnes de la table de données et transmettez-le en tant que paramètre dans la commande SQL.

Dans la procédure stockée, vous pouvez soit joindre directement sur une clé unique (si toutes les lignes que vous mettez à jour existent), ou, si vous rencontrez une situation dans laquelle vous devez effectuer à la fois des mises à jour et des insertions, utilisez SQL Merge commande dans la procédure stockée pour gérer à la fois les mises à jour et les insertions, le cas échéant.

Microsoft a à la fois référence de la syntaxe et un article avec des exemples pour la fusion.

Pour la pièce .NET, il suffit de définir le type de paramètre sur SqlDbType.Structured et en définissant la valeur de ce paramètre sur la table de données contenant les enregistrements à mettre à jour.

Cette méthode offre l'avantage de la clarté et de la facilité de maintenance. Bien que certains moyens offrent des améliorations de performances (comme le déposer dans une table temporaire puis les parcourir au-dessus de cette table), je pense qu’ils sont moins bien lotis par la simplicité de laisser .NET et SQL gérer le transfert de la table et la mise à jour des enregistrements. BAISER.

36
Locke

Essayez SqlBulkTools disponible sur Nuget.

Disclaimer: Je suis l'auteur de cette bibliothèque.

var bulk = new BulkOperations();
var records = GetRecordsToUpdate();

using (TransactionScope trans = new TransactionScope())
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager
    .ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
    {
        bulk.Setup<MyTable>()
            .ForCollection(records)
            .WithTable("MyTable")
            .AddColumn(x => x.SomeColumn1)
            .AddColumn(x => x.SomeColumn2)
            .BulkUpdate()
            .MatchTargetOn(x => x.Identifier)
            .Commit(conn);
    }

    trans.Complete();
}  

Seuls 'SomeColumn1' et 'SomeColumn2' seront mis à jour. Plus d'exemples peuvent être trouvés ici

3
Greg R Taylor

Je voudrais insérer de nouvelles valeurs dans une table temporaire, puis faire une fusion avec la table de destination, quelque chose comme ceci:

MERGE [DestTable] AS D 
USING #SourceTable S
    ON D.ID = S.ID
WHEN MATCHED THEN 
    UPDATE SET ...
WHEN NOT MATCHED 
THEN INSERT (...) 
VALUES (...);
2
aggaton

Je ne suis pas sûr de comprendre le point que vous allez archiver ... Si votre question concerne le remplacement rapide de tout le contenu de la table, je préférerais utiliser truncate ( http://technet.Microsoft.com /en-us/library/ms177570.aspx ) et l'insertion en bloc d'une nouvelle partie de données. Mais cette approche ne fonctionnera que si vous n’avez aucune contrainte de clé étrangère.

Si vous voulez une vraie mise à jour, cherchez le réponse de Guillermo Gutiérrez .

2
Yaugen Vlasau

Vous pouvez essayer de créer une requête contenant toutes les données. Utilisez un case. Ça pourrait ressembler à ça

update your_table
set some_column = case when id = 1 then 'value of 1'
                       when id = 5 then 'value of 5'
                       when id = 7 then 'value of 7'
                       when id = 9 then 'value of 9'
                  end
where id in (1,5,7,9)
1
juergen d

J'opterais pour une approche TempTable car de cette façon, vous ne verrouillez rien. Mais si votre logique doit être uniquement frontale et que vous devez utiliser une copie en bloc, je voudrais essayer une approche de suppression/insertion mais dans le même SqlTransaction pour assurer une intégrité qui pourrait ressembler à ceci:

// ...

dt = ConvertToDataTable(list);

using (SqlConnection cnx = new SqlConnection(myConnectionString))
{
    using (SqlTranscation tran = cnx.BeginTransaction())
    {
        DeleteData(cnx, tran, list);

        using (SqlBulkCopy bulkcopy = new SqlBulkCopy(cnx, SqlBulkCopyOptions.Default, tran))
        {
            bulkcopy.BulkCopyTimeout = 660;
            bulkcopy.DestinationTableName = TabelName;
            bulkcopy.WriteToServer(dt);
        }

        tran.Commit();
    }
}
1
Mauro2