Atualizar fontes de dados com DataAdapters
O Update
método do é chamado para resolver alterações de um DataSet back para a fonte de DataAdapter dados. O Update
método, como o Fill
método, toma como argumentos uma instância de um DataSet
, e um objeto opcional DataTable ou DataTable
nome. A DataSet
instância é a DataSet
que contém as alterações que foram feitas e a identifica a DataTable
tabela da qual recuperar as alterações. Se não DataTable
for especificado, o primeiro DataTable
do DataSet
é usado.
Quando você chama o Update
método, o DataAdapter
analisa as alterações que foram feitas e executa o comando apropriado (INSERT, UPDATE ou DELETE). Quando o DataAdapter
encontra uma alteração para um DataRow, ele usa o InsertCommand, UpdateCommandou DeleteCommand para processar a alteração. Isso permite maximizar o desempenho do seu aplicativo ADO.NET especificando a sintaxe de comando em tempo de design e, sempre que possível, através do uso de procedimentos armazenados. Você deve definir explicitamente os comandos antes de chamar Update
. Se Update
for chamado e o comando apropriado não existir para uma atualização específica (por exemplo, não DeleteCommand
para linhas excluídas), uma exceção será lançada.
Nota
Se você estiver usando procedimentos armazenados do SQL Server para editar ou excluir dados usando um DataAdapter
, certifique-se de não usar SET NOCOUNT ON na definição de procedimento armazenado. Isso faz com que a contagem de linhas afetadas retornada seja zero, o que o DataAdapter
interpreta como um conflito de simultaneidade. Neste caso, um DBConcurrencyException será lançado.
Os parâmetros de comando podem ser usados para especificar valores de entrada e saída para uma instrução SQL ou procedimento armazenado para cada linha modificada em um DataSet
arquivo . Para obter mais informações, consulte Parâmetros DataAdapter.
Nota
É importante entender a diferença entre excluir uma linha em um DataTable e remover a linha. Quando você chama o Remove
método ou RemoveAt
, a linha é removida imediatamente. Quaisquer linhas correspondentes na fonte de dados de back-end não serão afetadas se você passar o DataTable
ou para um DataAdapter
e chamar Update
DataSet
. Quando você usa o Delete
método, a linha permanece no e é marcada DataTable
para exclusão. Se você passar o DataTable
ou DataSet
para um DataAdapter
e chamar Update
, a linha correspondente na fonte de dados de back-end será excluída.
Se seus DataTable
mapas para ou forem gerados a partir de uma única tabela de banco de dados, você poderá aproveitar o DbCommandBuilder objeto para gerar automaticamente os DeleteCommand
objetos , InsertCommand
e UpdateCommand
para o DataAdapter
. Para obter mais informações, consulte Gerando comandos com CommandBuilders.
Usando UpdatedRowSource para mapear valores para um DataSet
Você pode controlar como os valores retornados da fonte de dados são mapeados de volta para a DataTable
seguinte chamada para o método Update de um DataAdapter
, usando a UpdatedRowSource propriedade de um DbCommand objeto. Ao definir a UpdatedRowSource
propriedade como um dos valores de enumeração, você pode controlar se os DataAdapter
parâmetros de saída retornados UpdateRowSource pelos comandos são ignorados ou aplicados à linha alterada no DataSet
. Você também pode especificar se a primeira linha retornada (se existir) é aplicada à linha alterada no DataTable
.
A tabela a seguir descreve os diferentes valores da UpdateRowSource
enumeração e como eles afetam o comportamento de um comando usado com um DataAdapter
.
Enumeração UpdatedRowSource | Description |
---|---|
Both | Os parâmetros de saída e a primeira linha de um conjunto de resultados retornado podem ser mapeados para a linha alterada no DataSet . |
FirstReturnedRecord | Somente os dados na primeira linha de um conjunto de resultados retornado podem ser mapeados para a linha alterada no DataSet . |
None | Todos os parâmetros de saída ou linhas de um conjunto de resultados retornado são ignorados. |
OutputParameters | Somente os parâmetros de saída podem ser mapeados para a linha alterada no DataSet . |
O Update
método resolve suas alterações de volta para a fonte de dados, no entanto, outros clientes podem ter modificado dados na fonte de dados desde a última vez que você preencheu o DataSet
. Para atualizar os DataSet
dados atuais, use o DataAdapter
método and Fill
. Novas linhas serão adicionadas à tabela e informações atualizadas serão incorporadas às linhas existentes. O Fill
método determina se uma nova linha será adicionada ou uma linha existente será atualizada examinando os valores de chave primária das linhas no DataSet
e as linhas retornadas pelo SelectCommand
. Se o Fill
método encontrar um valor de chave primária para uma linha na que corresponde a DataSet
um valor de chave primária de uma linha nos resultados retornados pelo SelectCommand
, ele atualiza a linha existente com as informações da linha retornada pelo SelectCommand
e define o RowState da linha existente como Unchanged
. Se uma linha retornada pelo SelectCommand
tiver um valor de chave primária que não corresponda a nenhum dos valores de chave primária das linhas no DataSet
, o Fill
método adicionará uma nova linha com um RowState
de Unchanged
.
Nota
Se o SelectCommand
retorna os resultados de um OUTER JOIN, o DataAdapter
não definirá um PrimaryKey
valor para o resultado DataTable
. Você deve definir a PrimaryKey
si mesmo para garantir que as linhas duplicadas sejam resolvidas corretamente. Para obter mais informações, consulte Definindo chaves primárias.
Para lidar com exceções que podem ocorrer ao chamar o Update
método, você pode usar o RowUpdated
evento para responder a erros de atualização de linha à medida que eles ocorrem (consulte Manipulando eventos DataAdapter), ou você pode definir DataAdapter.ContinueUpdateOnError
como true
antes de chamar Update
, e responder às informações de erro armazenadas na RowError
propriedade de uma linha específica quando a atualização estiver concluída (consulte Informações de erro de linha).
Nota
Chamar o , ou fará com que todos os Original
valores de a DataRow
sejam substituídos pelos Current
valores do DataRow
.DataRow
DataTable
DataSet
AcceptChanges
Se os valores de campo que identificam a linha como exclusiva tiverem sido modificados, depois de chamar AcceptChanges
os Original
valores não corresponderão mais aos valores na fonte de dados. AcceptChanges
é chamado automaticamente para cada linha durante uma chamada para o método Update de um DataAdapter
. Você pode preservar os valores originais durante uma chamada para o método Update definindo primeiro a AcceptChangesDuringUpdate
propriedade do to false, ou criando um manipulador de DataAdapter
eventos para o RowUpdated
evento e definindo o Status como SkipCurrentRow. Para obter mais informações, consulte Mesclando conteúdo de DataSet e Manipulando eventos DataAdapter.
Exemplo
Os exemplos a seguir demonstram como executar atualizações em linhas modificadas definindo explicitamente o UpdateCommand
de a DataAdapter
e chamando seu Update
método. Observe que o parâmetro especificado na cláusula WHERE da instrução UPDATE está definido para usar o Original
valor do SourceColumn
. Isso é importante, porque o Current
valor pode ter sido modificado e pode não corresponder ao valor na fonte de dados. O Original
valor é o valor que foi usado para preencher o DataTable
da fonte de dados.
static void AdapterUpdate(string connectionString)
{
using (SqlConnection connection =
new(connectionString))
{
SqlDataAdapter dataAdapter = new(
"SELECT CategoryID, CategoryName FROM Categories",
connection)
{
UpdateCommand = new SqlCommand(
"UPDATE Categories SET CategoryName = @CategoryName " +
"WHERE CategoryID = @CategoryID", connection)
};
dataAdapter.UpdateCommand.Parameters.Add(
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter parameter = dataAdapter.UpdateCommand.Parameters.Add(
"@CategoryID", SqlDbType.Int);
parameter.SourceColumn = "CategoryID";
parameter.SourceVersion = DataRowVersion.Original;
DataTable categoryTable = new();
dataAdapter.Fill(categoryTable);
DataRow categoryRow = categoryTable.Rows[0];
categoryRow["CategoryName"] = "New Beverages";
dataAdapter.Update(categoryTable);
Console.WriteLine("Rows after update.");
foreach (DataRow row in categoryTable.Rows)
{
{
Console.WriteLine("{0}: {1}", row[0], row[1]);
}
}
}
}
Private Sub AdapterUpdate(ByVal connectionString As String)
Using connection As SqlConnection = New SqlConnection( _
connectionString)
Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
"SELECT CategoryID, CategoryName FROM dbo.Categories", _
connection)
adapter.UpdateCommand = New SqlCommand( _
"UPDATE Categories SET CategoryName = @CategoryName " & _
"WHERE CategoryID = @CategoryID", connection)
adapter.UpdateCommand.Parameters.Add( _
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName")
Dim parameter As SqlParameter = _
adapter.UpdateCommand.Parameters.Add( _
"@CategoryID", SqlDbType.Int)
parameter.SourceColumn = "CategoryID"
parameter.SourceVersion = DataRowVersion.Original
Dim categoryTable As New DataTable
adapter.Fill(categoryTable)
Dim categoryRow As DataRow = categoryTable.Rows(0)
categoryRow("CategoryName") = "New Beverages"
adapter.Update(categoryTable)
Console.WriteLine("Rows after update.")
Dim row As DataRow
For Each row In categoryTable.Rows
Console.WriteLine("{0}: {1}", row(0), row(1))
Next
End Using
End Sub
Colunas de AutoIncremento
Se as tabelas da fonte de dados tiverem colunas de incremento automático, você poderá preencher as colunas retornando DataSet
o valor de incremento automático como um parâmetro de saída de um procedimento armazenado e mapeando-o para uma coluna em uma tabela, retornando o valor de incremento automático na primeira linha de um conjunto de resultados retornado por um procedimento armazenado ou instrução SQL, ou usando o RowUpdated
evento do DataAdapter
para executar uma instrução SELECT adicional. Para obter mais informações e um exemplo, consulte Recuperando valores de identidade ou de numeração automática.
Ordenação de inserções, atualizações e exclusões
Em muitas circunstâncias, a ordem em que as alterações feitas através do DataSet
são enviadas para a fonte de dados é importante. Por exemplo, se um valor de chave primária para uma linha existente for atualizado e uma nova linha tiver sido adicionada com o novo valor de chave primária como uma chave estrangeira, é importante processar a atualização antes da inserção.
Você pode usar o Select
método do DataTable
para retornar uma DataRow
matriz que só faz referência a linhas com um determinado RowState
. Em seguida, você pode passar a matriz retornada DataRow
para o Update
método do DataAdapter
para processar as linhas modificadas. Ao especificar um subconjunto de linhas a serem atualizadas, você pode controlar a ordem na qual as inserções, atualizações e exclusões são processadas.
Por exemplo, o código a seguir garante que as linhas excluídas da tabela sejam processadas primeiro, depois as linhas atualizadas e, em seguida, as linhas inseridas.
Dim table As DataTable = dataSet.Tables("Customers")
' First process deletes.
dataSet.Update(table.Select(Nothing, Nothing, _
DataViewRowState.Deleted))
' Next process updates.
adapter.Update(table.Select(Nothing, Nothing, _
DataViewRowState.ModifiedCurrent))
' Finally, process inserts.
adapter.Update(table.Select(Nothing, Nothing, _
DataViewRowState.Added))
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));
Usar um DataAdapter para recuperar e atualizar dados
Você pode usar um DataAdapter para recuperar e atualizar os dados.
O exemplo usa DataAdapter.AcceptChangesDuringFill para clonar os dados no banco de dados. Se a propriedade for definida como false, AcceptChanges não será chamado ao preencher a tabela e as linhas recém-adicionadas serão tratadas como linhas inseridas. Portanto, o exemplo usa essas linhas para inserir as novas linhas no banco de dados.
Os exemplos usam DataAdapter.TableMappings para definir o mapeamento entre a tabela de origem e DataTable.
O exemplo usa DataAdapter.FillLoadOption para determinar como o adaptador preenche a DataTable do DbDataReader. Quando você cria uma DataTable, você só pode gravar os dados do banco de dados para a versão atual ou a versão original definindo a propriedade como LoadOption.Upsert ou LoadOption.PreserveChanges.
O exemplo também atualizará a tabela usando DbDataAdapter.UpdateBatchSize para executar operações em lote.
Antes de compilar e executar o exemplo, você precisa criar o banco de dados de exemplo:
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 System.Data.SqlClient;
using System.Linq;
using CSDataAdapterOperations.Properties;
namespace CSDataAdapterOperations.Properties {
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
public string MySchoolConnectionString {
get {
return ((string)(this["MySchoolConnectionString"]));
}
}
}
}
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);
}
}
}