Aggiornare origini dati con DataAdapter

Si applica a: .NET Framework .NET Standard

Scarica ADO.NET

Il metodo Update di DataAdapter viene chiamato per applicare le modifiche apportate a un oggetto DataSet nell'origine dati. Il metodo Update, analogamente al metodo Fill, accetta come argomenti un'istanza di un oggetto DataSet e un oggetto DataTable o nome di DataTable facoltativi. L'istanza di DataSet rappresenta l'oggetto DataSet contenente le modifiche che sono state apportate e l'oggetto DataTable identifica la tabella da cui recuperare le modifiche. Se non viene specificato nessun oggetto DataTable, verrà usato il primo oggetto DataTable di DataSet.

Quando si chiama il metodo Update, DataAdapter analizza le modifiche apportate ed esegue il comando appropriato (INSERT, UPDATE o DELETE). Quando rileva una modifica a un oggetto DataAdapter, DataRow elabora la modifica usando InsertCommand, UpdateCommand o DeleteCommand.

Queste proprietà consentono di ottimizzare le prestazioni dell'applicazione ADO.NET specificando la sintassi del comando in fase di progettazione e, se possibile, tramite l'uso di stored procedure. È necessario impostare in modo esplicito i comandi prima di chiamare Update. Se Update viene chiamato e non esiste il comando appropriato per un determinato aggiornamento, ad esempio nessun DeleteCommand per le righe cancellate, viene generata un'eccezione.

Importante

Se si usano le stored procedure di SQL Server per modificare o eliminare i dati usando una classe DataAdapter, assicurarsi di non usare SET NOCOUNT ON nella definizione della stored procedure. Con tale comando il totale restituito delle righe interessate è pari a zero e tale situazione viene interpretata da DataAdapter come un conflitto di concorrenza. In questo caso verrà generata un'eccezione DBConcurrencyException.

È possibile usare i parametri Command per specificare i valori di input e di output di un'istruzione SQL o una stored procedure per ogni riga modificata in un DataSet. Per altre informazioni, vedere Parametri per DataAdapter.

Nota

È importante capire la differenza tra eliminazione di una riga in un oggetto DataTable e rimozione della riga. Quando si chiama il metodo Remove o RemoveAt, la riga viene rimossa immediatamente. Eventuali righe corrispondenti nell'origine dati back-end non saranno interessate se si passa DataTable o DataSet a DataAdapter e si chiama Update. Quando si usa il metodo Delete, la riga rimane in DataTable e viene contrassegnato per l'eliminazione. Se quindi si passa DataTable o DataSet a DataAdapter e si chiama Update, la riga corrispondente nell'origine dati back-end viene eliminata.

Se DataTable esegue il mapping o viene generato da una singola tabella di database, è possibile usare l'oggetto DbCommandBuilder per generare automaticamente gli oggetti DeleteCommand, InsertCommand e UpdateCommand di DataAdapter. Per altre informazioni, vedere Generazione dei comandi con CommandBuilders.

Usare UpdatedRowSource per eseguire il mapping dei valori a DataSet

È possibile controllare come viene eseguito il mapping dei valori restituiti dall'origine dati a DataTable in seguito a una chiamata al metodo Update di DataAdapter, usando la proprietà UpdatedRowSource di un oggetto SqlCommand. Impostando la proprietà UpdatedRowSource su uno dei valori di enumerazione UpdateRowSource, è possibile controllare se i parametri restituiti dai comandi di DataAdapter vengono ignorati o applicati alla riga modificata in DataSet. È possibile inoltre specificare se la prima riga restituita (se esiste) viene applicata alla riga modificata in DataTable.

La tabella seguente descrive i diversi valori dell'enumerazione UpdateRowSource e viene illustrato il modo in cui influenzano il comportamento di un comando usato con un DataAdapter.

Enumerazione UpdatedRowSource Descrizione
Both È possibile eseguire il mapping dei parametri di output e della prima riga di un set di risultati restituiti sulla riga modificata nel DataSet.
FirstReturnedRecord È possibile eseguire il mapping sulla riga modificata nel DataSet solo dei dati nella prima riga di un set di risultati restituiti.
None Tutti i parametri di output, o righe, di un set di risultati restituiti vengono ignorati.
OutputParameters È possibile eseguire il mapping sulla riga modificata nel DataSet solo dei parametri di output.

Il metodo Update applica le modifiche nell'origine dati, ma è possibile che altri client abbiano modificato i dati nell'origine dati dall'ultima volta che è stato compilato il DataSet. Per aggiornare DataSet con i dati correnti, usare i metodi DataAdapter e Fill. Le nuove righe verranno aggiunte alla tabella e le informazioni aggiornate verranno inserite nelle righe esistenti.

Quando si esegue il metodo Fill, verrà determinato se aggiungere una nuova riga o aggiornare una riga esistente esaminando i valori delle chiavi primarie delle righe del DataSet e delle righe restituite da SelectCommand. Se il metodo Fill rileva un valore di chiave primaria di una riga del DataSet che corrisponde al valore di chiave primaria di una riga presente nei risultati restituiti da SelectCommand, la riga esistente verrà aggiornata con le informazioni presenti nella riga restituita da SelectCommand e la proprietà RowState della riga esistente verrà impostata su Unchanged. Se una riga restituita da SelectCommand presenta un valore di chiave primaria che non corrisponde ad alcuno dei valori di chiave primaria delle righe del DataSet, il metodo Fill aggiungerà una nuova riga con il valore relativo a RowState impostato su Unchanged.

Nota

Se SelectCommand restituisce i risultati di OUTER JOIN, DataAdapter non imposterà un valore PrimaryKey per la classe DataTable risultante. Per assicurarsi che le righe duplicate vengano risolte correttamente, sarà necessario definire PrimaryKey in modo autonomo.

Per gestire le eccezioni che possono verificarsi quando si chiama il metodo Update, è possibile usare l'evento RowUpdated per rispondere agli errori di aggiornamento delle righe nel momento in cui si verificano (vedere Gestire eventi DataAdapter) oppure impostare ContinueUpdateOnError su true prima di chiamare il metodo Update e rispondere alle informazioni sugli errori archiviate nella proprietà RowError di una determinata riga al termine dell'aggiornamento.

Nota

Se si chiama AcceptChanges sull'oggetto DataSet, DataTable o DataRow, tutti i valori Original relativi a un oggetto DataRow verranno sovrascritti con i valori Current relativi a DataRow. Se i valori di campo che identificano la riga come univoca sono stati modificati, dopo la chiamata a AcceptChanges i valori Original non corrisponderanno più ai valori dell'origine dati. AcceptChanges viene chiamato automaticamente per ogni riga durante una chiamata al metodo Update di DataAdapter. Per mantenere i valori originali durante una chiamata al metodo Update, impostare prima la proprietà AcceptChangesDuringUpdate di DataAdapter su false oppure creare un gestore eventi per l'evento RowUpdated e impostare Status su SkipCurrentRow. Per altre informazioni, vedere Gestire eventi DataAdapter.

Gli esempi seguenti illustrano come aggiornare le righe modificate impostando in modo esplicito UpdateCommand di DataAdapter e chiamando il relativo metodo Update.

Nota

Il parametro specificato in WHERE clause di UPDATE statement è impostato in modo da usare il valore Original di SourceColumn. Questo è importante in quanto è possibile che il valore Current sia stato modificato e che non corrisponda più al valore nell'origine dati. Il valore Original è il valore che è stato usato per compilare DataTable dall'origine dati.

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]);
            }
        }
    }
}

Colonne AutoIncrement

Se nelle tabelle dell'origine dati sono presenti colonne con incremento automatico, è possibile riempire le colonne nel DataSet restituendo il valore dell'incremento automatico come un parametro di output di una stored procedure ed eseguendone il mapping su una colonna della tabella, restituendo il valore dell'incremento automatico nella prima riga di un set di risultati restituito da una stored procedure o un'istruzione SQL oppure usando l'evento RowUpdated di DataAdapter per eseguire un'ulteriore istruzione SELECT. Per altre informazioni e un esempio, vedere Recuperare i valori Identity o di numerazione automatica.

Ordinamento di inserimenti, aggiornamenti ed eliminazioni

In molti casi l'ordine in cui le modifiche apportate mediante DataSet vengono inviate all'origine dati è importante. Se ad esempio il valore di una chiave primaria relativo a una riga esistente viene aggiornato ed è stata aggiunta una nuova riga con il nuovo valore della chiave primaria come chiave esterna, è importante elaborare l'aggiornamento prima di effettuare l'inserimento.

È possibile usare il metodo Select di DataTable per restituire una matrice DataRow che faccia riferimento solo a righe con un particolare RowState. È quindi possibile passare la matrice DataRow restituita al metodo Update di DataAdapter per elaborare le righe modificate. Se si specifica un subset di righe da aggiornare, è possibile controllare l'ordine in cui vengono elaborati gli inserimenti, gli aggiornamenti e le eliminazioni.

Esempio

Il codice seguente, ad esempio, assicura che vengano elaborate prima le righe cancellate della tabella, quindi le righe aggiornate e infine le righe inserite.

// 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));

Usare DataAdapter per recuperare e aggiornare i dati

È possibile usare un oggetto DataAdapter per recuperare e aggiornare i dati.

  • L'esempio usa DataAdapter.AcceptChangesDuringFill per clonare i dati nel database. Se la proprietà è impostata su false, AcceptChanges non viene chiamato quando si compila la tabella e le righe appena aggiunte vengono considerate come righe inserite. Di conseguenza, nell'esempio queste righe vengono usate per inserire nuove righe nel database.

  • Gli esempi usano DataAdapter.TableMappings per definire il mapping tra la tabella di origine e DataTable.

  • L'esempio usa DataAdapter.FillLoadOption per determinare come l'adapter riempie DataTable da DbDataReader. Quando si crea un oggetto DataTable, è possibile scrivere i dati solo dal database alla versione corrente o alla versione originale impostando la proprietà su LoadOption.Upsert o LoadOption.PreserveChanges.

  • L'esempio aggiornerà anche la tabella usando DbDataAdapter.UpdateBatchSize per eseguire operazioni batch.

Prima di compilare ed eseguire l'esempio, è necessario creare il database di esempio:

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"]));
    }
}

Vedi anche