Mise à jour de sources de données avec des DataAdapter

S'applique à : .NET Framework .NET .NET Standard

Télécharger ADO.NET

La méthode Update de l'objet DataAdapter est appelée pour répercuter les modifications d'un objet DataSet dans la source de données. La méthode Update, comme la méthode Fill, prend comme arguments une instance d’un DataSet et un objet DataTable optionnel ou un nom de DataTable. L'instance DataSet est le DataSet qui contient les modifications apportées et le DataTable identifie la table de laquelle les modifications doivent être récupérées. Si aucun DataTable n'est spécifié, le premier DataTable du DataSet est utilisé.

Lorsque vous appelez la méthode Update, le DataAdapter analyse les modifications apportées et exécute la commande appropriée (INSERT, UPDATE ou DELETE). Lorsque le DataAdapter rencontre une modification apportée à un objet DataRow, il utilise la propriété InsertCommand, UpdateCommand, ou DeleteCommand pour traiter la modification.

Ces propriétés vous permettent d’optimiser les performances de votre application ADO.NET en spécifiant la syntaxe des commandes au moment de la conception et, là où c’est possible, via l’utilisation de procédures stockées. Vous devez explicitement définir les commandes avant d'appeler la méthode Update. Si la méthode Update est appelée et si la commande appropriée n'existe pas pour une mise à jour particulière (par exemple, pas de DeleteCommand pour les lignes supprimées), une exception est levée.

Important

Si vous utilisez des procédures stockées SQL Server pour modifier ou supprimer des données en utilisant un DataAdapter, veillez à ne pas utiliser SET NOCOUNT ON dans la définition de la procédure stockée. En effet, le nombre de lignes affectées retourné serait alors la valeur zéro, ce que DataAdapter interprète comme un conflit d'accès concurrentiel. Dans ce cas, l'exception DBConcurrencyException est levée.

Les paramètres de commande peuvent être utilisés pour spécifier des valeurs d’entrée et de sortie pour une instruction SQL ou une procédure stockée pour chaque ligne modifiée dans un DataSet. Pour plus d’informations, consultez Paramètres du DataAdapter.

Notes

Il est important de comprendre la différence entre la suppression d'une ligne dans un objet DataTable et la suppression de la ligne. Lorsque vous appelez la méthode Remove ou RemoveAt, la ligne est immédiatement supprimée. Les lignes correspondantes dans la source de données du back-end ne seront pas affectées si vous passez ensuite la DataTable ou le DataSet à un DataAdapter et que vous appelez Update. Lorsque vous utilisez la méthode Delete, la ligne reste dans le DataTable et est marquée pour suppression. Si vous passez ensuite la DataTable ou le DataSet à un DataAdapter et que vous appelez Update, la ligne correspondante dans la source de données du back-end est supprimée.

Si votre DataTable est mappé à une table de base de données unique ou est généré par celle-ci, vous pouvez tirer parti de l'objet DbCommandBuilder pour générer automatiquement les objets DeleteCommand, InsertCommand et UpdateCommand du DataAdapter. Pour plus d’informations, consultez Génération de commandes avec CommandBuilders.

Utiliser UpdatedRowSource pour mapper des valeurs à un DataSet

Vous pouvez contrôler comment les valeurs retournées depuis la source de données sont mappées à la DataTable après un appel à la méthode Update d’un DataAdapter, en utilisant la propriété UpdatedRowSource d’un objet SqlCommand. En affectant l'une des valeurs d'énumération UpdatedRowSource à la propriété UpdateRowSource, vous pouvez contrôler si les paramètres de sortie retournés par les commandes DataAdapter sont ignorés ou appliqués à la ligne modifiée dans le DataSet. Vous pouvez aussi spécifier si la première ligne retournée (si elle existe) doit être appliquée à la ligne modifiée dans le DataTable.

Le tableau suivant décrit les différentes valeurs de l'énumération UpdateRowSource et la façon dont elles affectent le comportement d'une commande utilisée avec un DataAdapter.

Énumération UpdatedRowSource Description
Both Les paramètres de sortie et la première ligne d'un jeu de résultats retourné peuvent être mappés à la ligne modifiée dans le DataSet.
FirstReturnedRecord Seules les données de la première ligne d'un jeu de résultats retourné peuvent être mappées à la ligne modifiée dans le DataSet.
None Les paramètres de sortie ou les lignes d'un jeu de résultats retourné sont ignorés.
OutputParameters Seuls les paramètres de sortie peuvent être mappés à la ligne modifiée dans le DataSet.

La méthode Update répercute vos modifications dans la source de données, cependant d'autres clients peuvent avoir modifié les données au niveau de la source depuis la dernière fois où vous avez rempli le DataSet. Pour actualiser votre DataSet avec des données en cours, utilisez le DataAdapter et la méthode Fill. De nouvelles lignes seront ajoutées à la table et les informations mises à jour seront incorporées dans les lignes existantes.

La méthode Fill détermine si une nouvelle ligne sera ajoutée ou si une ligne existante sera mise à jour en se basant sur les valeurs de clé primaire des lignes du DataSet et des lignes retournées par SelectCommand. Si une valeur de clé primaire d'une ligne du Fill correspond à celle d'une ligne des résultats retournés par DataSet, la méthode SelectCommand met à jour la ligne existante en y insérant les informations de la ligne retournée par SelectCommand et affecte la valeur RowState à la propriété Unchanged. Si la valeur de clé primaire d'une ligne retournée par SelectCommand n'a pas de correspondance dans les lignes du DataSet, la méthode Fill ajoute une nouvelle ligne avec un RowState ayant la valeur Unchanged.

Notes

Si la SelectCommand retourne les résultats d’une OUTER JOIN, le DataAdapter ne va pas définir une valeur PrimaryKey pour la DataTablerésultante. Vous devez définir vous-même la PrimaryKey pour garantir une résolution correcte des lignes dupliquées.

Pour gérer les exceptions qui peuvent se produire lors de l’appel de la méthode Update, vous pouvez utiliser l’événement RowUpdated pour répondre aux erreurs de mise à jour des lignes (voir Gérer les événements de DataAdapter), ou vous pouvez définir ContinueUpdateOnError sur true avant d’appeler Update et répondre aux informations d’erreur stockées dans la propriété RowError d’une ligne particulière quand la mise à jour est terminée.

Notes

L’appel de AcceptChanges sur le DataSet, la DataTable ou la DataRow provoquera le remplacement de toutes les valeurs Original d’une DataRow par les valeurs Current de la DataRow. Si les valeurs de champ qui identifient la ligne comme étant unique ont été modifiées, après l'appel de AcceptChanges, les valeurs Original ne correspondront plus aux valeurs de la source de données. AcceptChanges est appelé automatiquement pour chaque ligne lors d’un appel à la méthode Update d’un DataAdapter. Vous pouvez conserver les valeurs d'origine au cours d'un appel de la méthode Update en commençant par affecter la valeur false à la propriété AcceptChangesDuringUpdate du DataAdapter, ou en créant un gestionnaire d'événements pour l'événement RowUpdated et en affectant la valeur Status à SkipCurrentRow. Pour plus d’informations, consultez Gérer les événements de DataAdapter.

Les exemples suivants montrent comment effectuer des mises à jour de lignes modifiées en définissant explicitement la UpdateCommand d’un DataAdapter et en appelant sa méthode Update.

Notes

Le paramètre spécifié dans la WHERE clause de la UPDATE statement est défini pour utiliser la valeur Original de la SourceColumn. Cela est important car la valeur Current peut avoir été modifiée et ne pas correspondre à la valeur dans la source de données. La valeur Original est la valeur qui a été utilisée pour remplir le DataTable à partir de la source de données.

private static void AdapterUpdate(string connectionString)
{
    using (SqlConnection connection =
               new SqlConnection(connectionString))
    {
        SqlDataAdapter dataAdpater = new SqlDataAdapter(
          "SELECT CategoryID, CategoryName FROM Categories",
          connection);

        dataAdpater.UpdateCommand = new SqlCommand(
           "UPDATE Categories SET CategoryName = @CategoryName " +
           "WHERE CategoryID = @CategoryID", connection);

        dataAdpater.UpdateCommand.Parameters.Add(
           "@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");

        SqlParameter parameter = dataAdpater.UpdateCommand.Parameters.Add(
          "@CategoryID", SqlDbType.Int);
        parameter.SourceColumn = "CategoryID";
        parameter.SourceVersion = DataRowVersion.Original;

        DataTable categoryTable = new DataTable();
        dataAdpater.Fill(categoryTable);

        DataRow categoryRow = categoryTable.Rows[0];
        categoryRow["CategoryName"] = "New Beverages";

        dataAdpater.Update(categoryTable);

        Console.WriteLine("Rows after update.");
        foreach (DataRow row in categoryTable.Rows)
        {
            {
                Console.WriteLine("{0}: {1}", row[0], row[1]);
            }
        }
    }
}

AutoIncrement (colonnes)

Si les tables de votre source de données ont des colonnes à incrémentation automatique, vous pouvez remplir les colonnes de votre DataSet soit en retournant la valeur d'auto-incrémentation comme paramètre de sortie d'une procédure stockée et en la mappant à une colonne dans une table, soit en utilisant l'événement RowUpdated du DataAdapter pour exécuter une instruction SELECT supplémentaire. Pour plus d’informations et pour obtenir un exemple, consultez Récupérer des valeurs d’identité ou de numérotation automatique.

Ordre des insertions, des mises à jour et des suppressions

Dans de nombreuses circonstances, l'ordre dans lequel les modifications apportées dans le DataSet sont transmises à la source de données est important. Par exemple, si une valeur de clé primaire d'une ligne existante est mise à jour et qu'une nouvelle ligne est ajoutée avec la nouvelle valeur de clé primaire en tant que clé primaire, il est important de traiter la mise à jour avant l'insertion.

Vous pouvez utiliser la méthode Select du DataTable pour retourner un tableau DataRow qui fait uniquement référence à des lignes ayant un RowState particulier. Vous pouvez ensuite passer le tableau DataRow retourné à la méthode Update du DataAdapter pour traiter les lignes modifiées. En spécifiant un sous-ensemble des lignes à mettre à jour, vous pouvez contrôler l'ordre dans lequel les insertions, mises à jour et suppressions sont traitées.

Exemple

Le code suivant garantit, par exemple, que seront d'abord traitées les lignes supprimées de la table puis les lignes mises à jour et enfin les lignes insérées.

// Assumes that dataSet and adapter are valid objects.
DataTable table = dataSet.Tables["Customers"];

// First process deletes.
adapter.Update(table.Select(null, null, DataViewRowState.Deleted));

// Next process updates.
adapter.Update(table.Select(null, null,
    DataViewRowState.ModifiedCurrent));

// Finally, process inserts.
adapter.Update(table.Select(null, null, DataViewRowState.Added));

Utiliser un DataAdapter pour récupérer et mettre à jour des données

Vous pouvez utiliser un DataAdapter pour récupérer et mettre à jour les données.

  • L’exemple utilise DataAdapter.AcceptChangesDuringFill pour cloner les données dans la base de données. Si la propriété est définie sur false, AcceptChanges n’est pas appelée lors du remplissage de la table, et les lignes nouvellement ajoutées sont traitées comme des lignes insérées. Ainsi, l'exemple utilise ces lignes pour insérer de nouvelles lignes dans la base de données.

  • L’exemple utilise DataAdapter.TableMappings pour définir le mappage entre la table source et la DataTable.

  • L’exemple utilise DataAdapter.FillLoadOption pour déterminer comment l’adaptateur remplit la DataTable à partir du DbDataReader. Quand vous créez une DataTable, vous pouvez seulement écrire les données de la base de données vers la version actuelle ou la version d’origine en définissant la propriété sur LoadOption.Upsert ou LoadOption.PreserveChanges.

  • L’exemple met également à jour la table en utilisant DbDataAdapter.UpdateBatchSize pour effectuer les opérations de traitement par lot.

Avant de compiler et d'exécuter l'exemple, vous devez créer l'exemple de base de données :

USE [master]
GO

CREATE DATABASE [MySchool]

GO

USE [MySchool]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Course]([CourseID] [nvarchar](10) NOT NULL,
[Year] [smallint] NOT NULL,
[Title] [nvarchar](100) NOT NULL,
[Credits] [int] NOT NULL,
[DepartmentID] [int] NOT NULL,
 CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED
(
[CourseID] ASC,
[Year] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Department]([DepartmentID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Budget] [money] NOT NULL,
[StartDate] [datetime] NOT NULL,
[Administrator] [int] NULL,
 CONSTRAINT [PK_Department] PRIMARY KEY CLUSTERED
(
[DepartmentID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]

GO

INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1045', 2012, N'Calculus', 4, 7)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1061', 2012, N'Physics', 4, 1)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2021', 2012, N'Composition', 3, 2)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2042', 2012, N'Literature', 4, 2)

SET IDENTITY_INSERT [dbo].[Department] ON

INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (1, N'Engineering', 350000.0000, CAST(0x0000999C00000000 AS DateTime), 2)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (2, N'English', 120000.0000, CAST(0x0000999C00000000 AS DateTime), 6)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (4, N'Economics', 200000.0000, CAST(0x0000999C00000000 AS DateTime), 4)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (7, N'Mathematics', 250024.0000, CAST(0x0000999C00000000 AS DateTime), 3)
SET IDENTITY_INSERT [dbo].[Department] OFF

ALTER TABLE [dbo].[Course]  WITH CHECK ADD  CONSTRAINT [FK_Course_Department] FOREIGN KEY([DepartmentID])
REFERENCES [dbo].[Department] ([DepartmentID])
GO
ALTER TABLE [dbo].[Course] CHECK CONSTRAINT [FK_Course_Department]
GO
using System;
using System.Data;
using System.Data.Common;
using Microsoft.Data.SqlClient;
using System.Linq;
using CSDataAdapterOperations.Properties;

class Program
{
    static void Main(string[] args)
    {
        Settings settings = new Settings();

        // Copy the data from the database.  Get the table Department and Course from the database.
        String selectString = @"SELECT [DepartmentID],[Name],[Budget],[StartDate],[Administrator]
                                     FROM [MySchool].[dbo].[Department];

                                   SELECT [CourseID],@Year as [Year],Max([Title]) as [Title],
                                   Max([Credits]) as [Credits],Max([DepartmentID]) as [DepartmentID]
                                   FROM [MySchool].[dbo].[Course]
                                   Group by [CourseID]";

        DataSet mySchool = new DataSet();

        SqlCommand selectCommand = new SqlCommand(selectString);
        SqlParameter parameter = selectCommand.Parameters.Add("@Year", SqlDbType.SmallInt, 2);
        parameter.Value = new Random(DateTime.Now.Millisecond).Next(9999);

        // Use DataTableMapping to map the source tables and the destination tables.
        DataTableMapping[] tableMappings = { new DataTableMapping("Table", "Department"), new DataTableMapping("Table1", "Course") };
        CopyData(mySchool, settings.MySchoolConnectionString, selectCommand, tableMappings);

        Console.WriteLine("The following tables are from the database.");
        foreach (DataTable table in mySchool.Tables)
        {
            Console.WriteLine(table.TableName);
            ShowDataTable(table);
        }

        // Roll back the changes
        DataTable department = mySchool.Tables["Department"];
        DataTable course = mySchool.Tables["Course"];

        department.Rows[0]["Name"] = "New" + department.Rows[0][1];
        course.Rows[0]["Title"] = "New" + course.Rows[0]["Title"];
        course.Rows[0]["Credits"] = 10;

        Console.WriteLine("After we changed the tables:");
        foreach (DataTable table in mySchool.Tables)
        {
            Console.WriteLine(table.TableName);
            ShowDataTable(table);
        }

        department.RejectChanges();
        Console.WriteLine("After use the RejectChanges method in Department table to roll back the changes:");
        ShowDataTable(department);

        DataColumn[] primaryColumns = { course.Columns["CourseID"] };
        DataColumn[] resetColumns = { course.Columns["Title"] };
        ResetCourse(course, settings.MySchoolConnectionString, primaryColumns, resetColumns);
        Console.WriteLine("After use the ResetCourse method in Course table to roll back the changes:");
        ShowDataTable(course);

        // Batch update the table.
        String insertString = @"Insert into [MySchool].[dbo].[Course]([CourseID],[Year],[Title],
                                   [Credits],[DepartmentID])
             values (@CourseID,@Year,@Title,@Credits,@DepartmentID)";
        SqlCommand insertCommand = new SqlCommand(insertString);
        insertCommand.Parameters.Add("@CourseID", SqlDbType.NVarChar, 10, "CourseID");
        insertCommand.Parameters.Add("@Year", SqlDbType.SmallInt, 2, "Year");
        insertCommand.Parameters.Add("@Title", SqlDbType.NVarChar, 100, "Title");
        insertCommand.Parameters.Add("@Credits", SqlDbType.Int, 4, "Credits");
        insertCommand.Parameters.Add("@DepartmentID", SqlDbType.Int, 4, "DepartmentID");

        const Int32 batchSize = 10;
        BatchInsertUpdate(course, settings.MySchoolConnectionString, insertCommand, batchSize);
    }

    private static void CopyData(DataSet dataSet, String connectionString, SqlCommand selectCommand, DataTableMapping[] tableMappings)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            selectCommand.Connection = connection;

            connection.Open();

            using (SqlDataAdapter adapter = new SqlDataAdapter(selectCommand))
            {
                adapter.TableMappings.AddRange(tableMappings);
                // If set the AcceptChangesDuringFill as the false, AcceptChanges will not be called on a
                // DataRow after it is added to the DataTable during any of the Fill operations.
                adapter.AcceptChangesDuringFill = false;

                adapter.Fill(dataSet);
            }
        }
    }

    // Roll back only one column or several columns data of the Course table by call ResetDataTable method.
    private static void ResetCourse(DataTable table, String connectionString,
        DataColumn[] primaryColumns, DataColumn[] resetColumns)
    {
        table.PrimaryKey = primaryColumns;

        // Build the query string
        String primaryCols = String.Join(",", primaryColumns.Select(col => col.ColumnName));
        String resetCols = String.Join(",", resetColumns.Select(col => $"Max({col.ColumnName}) as {col.ColumnName}"));

        String selectString = $"Select {primaryCols},{resetCols} from Course Group by {primaryCols}";

        SqlCommand selectCommand = new SqlCommand(selectString);

        ResetDataTable(table, connectionString, selectCommand);
    }

    // RejectChanges will roll back all changes made to the table since it was loaded, or the last time AcceptChanges
    // was called. When you copy from the database, you can lose all the data after calling RejectChanges
    // The ResetDataTable method rolls back one or more columns of data.
    private static void ResetDataTable(DataTable table, String connectionString,
        SqlCommand selectCommand)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            selectCommand.Connection = connection;

            connection.Open();

            using (SqlDataAdapter adapter = new SqlDataAdapter(selectCommand))
            {
                // The incoming values for this row will be written to the current version of each
                // column. The original version of each column's data will not be changed.
                adapter.FillLoadOption = LoadOption.Upsert;

                adapter.Fill(table);
            }
        }
    }

    private static void BatchInsertUpdate(DataTable table, String connectionString,
        SqlCommand insertCommand, Int32 batchSize)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            insertCommand.Connection = connection;
            // When setting UpdateBatchSize to a value other than 1, all the commands
            // associated with the SqlDataAdapter have to have their UpdatedRowSource
            // property set to None or OutputParameters. An exception is thrown otherwise.
            insertCommand.UpdatedRowSource = UpdateRowSource.None;

            connection.Open();

            using (SqlDataAdapter adapter = new SqlDataAdapter())
            {
                adapter.InsertCommand = insertCommand;
                // Gets or sets the number of rows that are processed in each round-trip to the server.
                // Setting it to 1 disables batch updates, as rows are sent one at a time.
                adapter.UpdateBatchSize = batchSize;

                adapter.Update(table);

                Console.WriteLine("Successfully to update the table.");
            }
        }
    }

    private static void ShowDataTable(DataTable table)
    {
        foreach (DataColumn col in table.Columns)
        {
            Console.Write("{0,-14}", col.ColumnName);
        }
        Console.WriteLine("{0,-14}", "RowState");

        foreach (DataRow row in table.Rows)
        {
            foreach (DataColumn col in table.Columns)
            {
                if (col.DataType.Equals(typeof(DateTime)))
                    Console.Write("{0,-14:d}", row[col]);
                else if (col.DataType.Equals(typeof(Decimal)))
                    Console.Write("{0,-14:C}", row[col]);
                else
                    Console.Write("{0,-14}", row[col]);
            }
            Console.WriteLine("{0,-14}", row.RowState);
        }
    }
}

namespace CSDataAdapterOperations.Properties
{
    internal sealed partial class Settings : System.Configuration.ApplicationSettingsBase
    {
        private static readonly Settings defaultInstance =
            ((Settings)(System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

        public static Settings Default => defaultInstance;

        [System.Configuration.ApplicationScopedSetting()]
        [System.Configuration.DefaultSettingValue("Data Source=(local);Initial Catalog=MySchool;Integrated Security=True")]
        public string MySchoolConnectionString => ((string)(this["MySchoolConnectionString"]));
    }
}

Voir aussi