Actualizar orígenes de datos con objetos DataAdapterUpdating Data Sources with DataAdapters

El método Update de DataAdapter se llama para reflejar en el origen de datos todos los cambios efectuados en DataSet.The Update method of the DataAdapter is called to resolve changes from a DataSet back to the data source. El método Update, al igual que el método Fill, acepta como argumentos una instancia de DataSet y, de forma opcional, un objeto DataTable o un nombre de DataTable.The Update method, like the Fill method, takes as arguments an instance of a DataSet, and an optional DataTable object or DataTable name. La instancia de DataSet es el DataSet que contiene los cambios efectuados, y DataTable identifica la tabla desde la que se pueden recuperar esos cambios.The DataSet instance is the DataSet that contains the changes that have been made, and the DataTable identifies the table from which to retrieve the changes. Si no se especifica DataTable, se utiliza el primer DataTable de DataSet.If no DataTable is specified, the first DataTable in the DataSet is used.

Al llamar al método Update, DataAdapter analiza los cambios efectuados y ejecuta el comando apropiado (INSERT, UPDATE o DELETE).When you call the Update method, the DataAdapter analyzes the changes that have been made and executes the appropriate command (INSERT, UPDATE, or DELETE). Cuando DataAdapter encuentra un cambio en DataRow, utiliza los comandos InsertCommand, UpdateCommand o DeleteCommand para reflejarlo.When the DataAdapter encounters a change to a DataRow, it uses the InsertCommand, UpdateCommand, or DeleteCommand to process the change. De esta forma, se obtiene el máximo rendimiento de la aplicación de ADO.NET al especificar la sintaxis del comando en la fase de diseño y utilizar, siempre que es posible, procedimientos almacenados.This allows you to maximize the performance of your ADO.NET application by specifying command syntax at design time and, where possible, through the use of stored procedures. Antes de llamar a Update deben establecerse de forma explícita los comandos.You must explicitly set the commands before calling Update. Si se llama a Update y el comando correspondiente a una actualización determinada no existe (por ejemplo, no hay un comando DeleteCommand para las filas eliminadas), se inicia una excepción.If Update is called and the appropriate command does not exist for a particular update (for example, no DeleteCommand for deleted rows), an exception is thrown.

Nota

Si está utilizando procedimientos almacenados de SQL Server para editar o eliminar datos con DataAdapter, asegúrese de que no utiliza SET NOCOUNT ON en la definición del procedimiento almacenado.If you are using SQL Server stored procedures to edit or delete data using a DataAdapter, make sure that you do not use SET NOCOUNT ON in the stored procedure definition. Esto hace que el recuento de filas afectadas vuelva a cero, lo que DataAdapter interpreta como un conflicto de simultaneidad.This causes the rows affected count returned to be zero, which the DataAdapter interprets as a concurrency conflict. En este caso, se iniciará una DBConcurrencyException.In this event, a DBConcurrencyException will be thrown.

Se pueden usar los parámetros de comando para especificar los valores de entrada y salida de una instrucción SQL o un procedimiento almacenado para cada fila modificada en DataSet.Command parameters can be used to specify input and output values for an SQL statement or stored procedure for each modified row in a DataSet. Para obtener más información, vea DataAdapter Parameters.For more information, see DataAdapter Parameters.

Nota

Es importante comprender la diferencia entre eliminar una fila de una DataTable y quitar la fila.It is important to understand the difference between deleting a row in a DataTable and removing the row. Al llamar al método Remove o RemoveAt, la fila se quita inmediatamente.When you call the Remove or RemoveAt method, the row is removed immediately. Cualquier fila correspondiente en el origen de datos back end no se verá afectada si a continuación se pasa DataTable o DataSet a DataAdapter y se llama a Update.Any corresponding rows in the back end data source will not be affected if you then pass the DataTable or DataSet to a DataAdapter and call Update. Al utilizar el método Delete, la fila permanece en DataTable y se marca para eliminación.When you use the Delete method, the row remains in the DataTable and is marked for deletion. Si a continuación se pasa DataTable o DataSet a DataAdapter y se llama a Update, la fila correspondiente en el origen de datos back end se elimina.If you then pass the DataTable or DataSet to a DataAdapter and call Update, the corresponding row in the back end data source is deleted.

Si DataTable está asignada a una única base de datos o se ha generado a partir de ella, puede utilizar el objeto DbCommandBuilder para generar automáticamente los objetos DeleteCommand, InsertCommand y UpdateCommand de DataAdapter.If your DataTable maps to or is generated from a single database table, you can take advantage of the DbCommandBuilder object to automatically generate the DeleteCommand, InsertCommand, and UpdateCommand objects for the DataAdapter. Para obtener más información, vea generar comandos con objetos CommandBuilder.For more information, see Generating Commands with CommandBuilders.

Utilizar UpdatedRowSource para asignar valores a DataSetUsing UpdatedRowSource to Map Values to a DataSet

Puede controlar la forma en que los valores devueltos desde el origen de datos se asignan a DataTable después de una llamada al método Update de DataAdapter, utilizando la propiedad UpdatedRowSource de un objeto DbCommand.You can control how the values returned from the data source are mapped back to the DataTable following a call to the Update method of a DataAdapter, by using the UpdatedRowSource property of a DbCommand object. Al asignar la propiedad UpdatedRowSource a uno de los valores de enumeración UpdateRowSource, puede determinar si los parámetros que devuelven los comandos DataAdapter se deben omitir o se deben aplicar a la fila cambiada en DataSet.By setting the UpdatedRowSource property to one of the UpdateRowSource enumeration values, you can control whether output parameters returned by the DataAdapter commands are ignored or applied to the changed row in the DataSet. También puede especificar si la primera fila devuelta (si existe) se aplica a la fila modificada en DataTable.You can also specify whether the first returned row (if it exists) is applied to the changed row in the DataTable.

En la tabla siguiente se describen los distintos valores de la enumeración UpdateRowSource y la forma en que afectan al comportamiento del comando utilizado con DataAdapter.The following table describes the different values of the UpdateRowSource enumeration and how they affect the behavior of a command used with a DataAdapter.

Enumeración UpdatedRowSourceUpdatedRowSource Enumeration DescripciónDescription
Both Tanto los parámetros de salida como la primera fila del conjunto de resultados devuelto se pueden asignar a la fila modificada en DataSet.Both the output parameters and the first row of a returned result set may be mapped to the changed row in the DataSet.
FirstReturnedRecord Solo los datos de la primera fila del conjunto de resultados devuelto se pueden asignar a la fila modificada en el DataSet.Only the data in the first row of a returned result set may be mapped to the changed row in the DataSet.
None Se pasan por alto todos los parámetros de salida y las filas del conjunto de resultados devuelto.Any output parameters or rows of a returned result set are ignored.
OutputParameters Solo los parámetros de salida se pueden asignar a la fila modificada en DataSet.Only output parameters may be mapped to the changed row in the DataSet.

El método Update vuelve a resolver los cambios en el origen de datos; sin embargo, puede que otros clientes hayan modificado datos en el origen de datos desde la última vez que se llenó DataSet.The Update method resolves your changes back to the data source; however other clients may have modified data at the data source since the last time you filled the DataSet. Para actualizar DataSet con datos actuales, utilice el DataAdapter y el método Fill.To refresh your DataSet with current data, use the DataAdapter and Fill method. De esta forma se agregan las filas nuevas a la tabla y se actualiza la información en las filas ya existentes.New rows will be added to the table, and updated information will be incorporated into existing rows. El método Fill determina si se va a agregar una nueva fila o si se va a actualizar una fila existente mediante el examen de los valores de clave principal de las filas de DataSet y las filas devueltas por SelectCommand.The Fill method determines whether a new row will be added or an existing row will be updated by examining the primary key values of the rows in the DataSet and the rows returned by the SelectCommand. Si el método Fill encuentra un valor de clave principal de una fila de DataSet que coincide con un valor de clave principal de una fila de los resultados devueltos por SelectCommand, éste actualiza la fila existente con la información de la fila devuelta por SelectCommand y establece el RowState de la fila existente en Unchanged.If the Fill method encounters a primary key value for a row in the DataSet that matches a primary key value from a row in the results returned by the SelectCommand, it updates the existing row with the information from the row returned by the SelectCommand and sets the RowState of the existing row to Unchanged. Si una fila devuelta por SelectCommand tiene un valor de clave principal que no coincide con ninguno de los valores de clave principal de las filas de DataSet, el método Fill agrega una nueva fila con un RowState de Unchanged.If a row returned by the SelectCommand has a primary key value that does not match any of the primary key values of the rows in the DataSet, the Fill method adds a new row with a RowState of Unchanged.

Nota

Si SelectCommand devuelve los resultados de una combinación externa (OUTER JOIN), DataAdapter no establecerá un valor PrimaryKey para la tabla DataTable resultante.If the SelectCommand returns the results of an OUTER JOIN, the DataAdapter will not set a PrimaryKey value for the resulting DataTable. Debe definir PrimaryKey para asegurarse de que las filas duplicadas se resuelven correctamente.You must define the PrimaryKey yourself to ensure that duplicate rows are resolved correctly. Para obtener más información, vea definir claves principales.For more information, see Defining Primary Keys.

Para controlar las excepciones que se pueden producir al llamar al Update método, puede utilizar el RowUpdated evento para responder a los errores de actualización de la fila a medida que se producen (vea controlar eventos DataAdapter), o puede establecer DataAdapter.ContinueUpdateOnError en true antes de llamar a Update y responder a la información de error almacenada en la RowError propiedad de una fila determinada cuando se completa la actualización (vea la información de error de fila).To handle exceptions that may occur when calling the Update method, you can use the RowUpdated event to respond to row update errors as they occur (see Handling DataAdapter Events), or you can set DataAdapter.ContinueUpdateOnError to true before calling Update, and respond to the error information stored in the RowError property of a particular row when the update is complete (see Row Error Information).

Nota

Llamar a AcceptChanges en DataSet , DataTable o hará DataRow que todos los Original valores de DataRow se sobrescriban con los Current valores de DataRow .Calling AcceptChanges on the DataSet, DataTable, or DataRow will cause all Original values for a DataRow to be overwritten with the Current values for the DataRow. Si se han modificado los valores de campo que identifican de forma única a una fila, los valores AcceptChanges dejarán de coincidir con los valores del origen de datos después de llamar a Original.If the field values that identify the row as unique have been modified, after calling AcceptChanges the Original values will no longer match the values in the data source. Se llama automáticamente a AcceptChanges para cada fila durante una llamada al método Update de DataAdapter.AcceptChanges is called automatically for each row during a call to the Update method of a DataAdapter. Puede conservar los valores originales durante una llamada al método Update estableciendo primero la propiedad AcceptChangesDuringUpdate de DataAdapter en false o creando un controlador de eventos para el evento RowUpdated y estableciendo Status en SkipCurrentRow.You can preserve the original values during a call to the Update method by first setting the AcceptChangesDuringUpdate property of the DataAdapter to false, or by creating an event handler for the RowUpdated event and setting the Status to SkipCurrentRow. Para obtener más información, vea combinar el contenido de un conjunto de datos y controlar eventos DataAdapter.For more information, see Merging DataSet Contents and Handling DataAdapter Events.

EjemploExample

En los siguientes ejemplos se muestra cómo realizar actualizaciones de las filas modificadas estableciendo explícitamente el valor UpdateCommand de DataAdapter y llamando a su Update método.The following examples demonstrate how to perform updates to modified rows by explicitly setting the UpdateCommand of a DataAdapter and calling its Update method. Observe cómo el parámetro especificado en la cláusula WHERE de la instrucción UPDATE tiene el valor adecuado para usar el valor Original de SourceColumn.Notice that the parameter specified in the WHERE clause of the UPDATE statement is set to use the Original value of the SourceColumn. Este hecho es muy importante ya que el valor Current puede haber sido modificado de forma que ya no coincida con el valor del origen de datos.This is important, because the Current value may have been modified and may not match the value in the data source. El valor Original es el que se usó para rellenar la tabla DataTable a partir del origen de datos.The Original value is the value that was used to populate the DataTable from the data source.

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

Columnas AutoIncrementAutoIncrement Columns

Si las tablas del origen de datos tienen columnas con incremento automático, puede rellenar las columnas de DataSet devolviendo el valor de incremento automático como un parámetro de salida de un procedimiento almacenado y asignándolo a una columna de la tabla, o devolviendo el valor de incremento automático de la primera fila de un conjunto de resultados devuelto por un procedimiento almacenado o una instrucción SQL, o mediante el evento RowUpdated de DataAdapter para ejecutar una instrucción SELECT adicional.If the tables from your data source have auto-incrementing columns, you can fill the columns in your DataSet either by returning the auto-increment value as an output parameter of a stored procedure and mapping that to a column in a table, by returning the auto-increment value in the first row of a result set returned by a stored procedure or SQL statement, or by using the RowUpdated event of the DataAdapter to execute an additional SELECT statement. Para obtener más información y un ejemplo, vea recuperar valores de identidad o Autonumérico.For more information and an example, see Retrieving Identity or Autonumber Values.

Orden de las inserciones, actualizaciones y eliminacionesOrdering of Inserts, Updates, and Deletes

En algunas circunstancias, es importante el orden en que se envían al origen de datos los cambios realizados en el DataSet.In many circumstances, the order in which changes made through the DataSet are sent to the data source is important. Por ejemplo, si se actualiza el valor de una clave principal de una fila existente y se ha agregado una nueva fila con el nuevo valor de la clave principal como una clave externa, es importante que la actualización de la fila se procese antes que la inserción.For example, if a primary key value for an existing row is updated, and a new row has been added with the new primary key value as a foreign key, it is important to process the update before the insert.

Puede usar el método Select de DataTable para devolver una matriz DataRow que solo haga referencia a filas con un estado RowState determinado.You can use the Select method of the DataTable to return a DataRow array that only references rows with a particular RowState. A continuación, puede pasar la matriz DataRow al método Update de DataAdapter para procesar las filas modificadas.You can then pass the returned DataRow array to the Update method of the DataAdapter to process the modified rows. Al especificar un subconjunto de filas que modificar, puede controlar el orden en que se procesan las inserciones, actualizaciones y eliminaciones.By specifying a subset of rows to be updated, you can control the order in which inserts, updates, and deletes are processed.

EjemploExample

Por ejemplo, en el código siguiente se garantiza que en primer lugar se realizan en la tabla las eliminaciones de filas, después las actualizaciones y finalmente las inserciones.For example, the following code ensures that the deleted rows of the table are processed first, then the updated rows, and then the inserted rows.

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 un objeto DataAdapter para recuperar y actualizar datosUse a DataAdapter to Retrieve and Update Data

Puede usar un objeto DataAdapter para recuperar y actualizar los datos.You can use a DataAdapter to retrieve and update the data.

  • El ejemplo usa DataAdapter.AcceptChangesDuringFill para clonar los datos en la base de datos.The sample uses DataAdapter.AcceptChangesDuringFill to clone the data in the database. Si la propiedad se establece como false, no se llama a AcceptChanges al rellenar la tabla y las filas que se acaban de agregar se tratan como filas insertadas.If the property is set as false, AcceptChanges is not called when filling the table, and the newly added rows are treated as inserted rows. Por lo tanto, el ejemplo usa estas filas para insertar las filas nuevas en la base de datos.So, the sample uses these rows to insert the new rows into the database.

  • Los ejemplos usan DataAdapter.TableMappings para definir la asignación entre la tabla de origen y DataTable.The samples uses DataAdapter.TableMappings to define the mapping between the source table and DataTable.

  • El ejemplo usa DataAdapter.FillLoadOption para determinar cómo rellena el adaptador el objeto DataTable de DbDataReader.The sample uses DataAdapter.FillLoadOption to determine how the adapter fills the DataTable from the DbDataReader. Al crear un objeto DataTable, solo puede escribir los datos de la base de datos en la versión actual o en la versión original si establece la propiedad como LoadOption.Upsert o LoadOption.PreserveChanges.When you create a DataTable, you can only write the data from database to the current version or the original version by setting the property as the LoadOption.Upsert or the LoadOption.PreserveChanges.

  • El ejemplo también actualizará la tabla mediante DbDataAdapter.UpdateBatchSize para realizar operaciones por lotes.The sample will also update the table by using DbDataAdapter.UpdateBatchSize to perform batch operations.

Antes de compilar y ejecutar el ejemplo, debe crear la base de datos de ejemplo:Before you compile and run the sample, you need to create the sample database:

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

Los proyectos de C# y Visual Basic con este ejemplo de código se pueden encontrar en ejemplos de código para desarrolladores.C# and Visual Basic projects with this code sample can be found on Developer Code Samples.

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()]
      [global::System.Configuration.DefaultSettingValueAttribute("Data Source=(local);Initial Catalog=MySchool;Integrated Security=True")]
      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);
      }
   }
}

Consulte tambiénSee also