Aggiornare origini dati con DataAdapter
Si applica a: .NET Framework .NET .NET Standard
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.
I parametri di comando possono essere usati per specificare i valori di input e output per un'istruzione SQL o una stored procedure per ogni riga modificata in un oggetto 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"]));
}
}