Bagikan melalui


Memperbarui sumber data dengan DataAdapters

Metode Update dipanggil DataAdapter untuk menyelesaikan perubahan dari DataSet kembali ke sumber data. Metode Update, seperti metode Fill, mengambil sebagai argumen instans dari DataSet, dan objek opsional DataTable atau nama DataTable. Instans DataSet adalah DataSet yang memuat perubahan yang telah dilakukan, dan DataTable mengidentifikasi tabel untuk mengambil perubahan. Jika DataTable tidak ditentukan, DataTable pertama dalam DataSet digunakan.

Ketika Anda memanggil metode Update, DataAdapter menganalisis perubahan yang telah dilakukan dan menjalankan perintah yang sesuai (SISIPKAN, PERBARUI, atau HAPUS). Ketika DataAdapter mengalami perubahan pada DataRow, maka menggunakan InsertCommand, UpdateCommand, atau DeleteCommand untuk memproses perubahan. Hal ini mengizinkan Anda untuk memaksimalkan performa aplikasi ADO.NET Anda dengan menentukan sintaks perintah pada waktu desain dan, apabila memungkinkan, melalui penggunaan prosedur tersimpan. Anda harus secara eksplisit mengatur perintah sebelum memanggil Update. Jika Update dipanggil dan perintah yang sesuai tidak ada untuk pembaruan tertentu (contohnya, bukan DeleteCommand untuk baris yang dihapus), pengecualian dilemparkan.

Catatan

Jika Anda menggunakan prosedur tersimpan SQL Server untuk mengedit atau menghapus data menggunakan DataAdapter, pastikan bahwa Anda tidak menggunakan SET NOCOUNT ON dalam definisi prosedur tersimpan. Ini menyebabkan jumlah baris yang terpengaruh dikembalikan menjadi nol, yang ditafsirkan DataAdapter sebagai konflik konkurensi. Dalam hal ini, DBConcurrencyException akan dilemparkan.

Parameter perintah dapat digunakan untuk menentukan nilai input dan output untuk pernyataan SQL atau prosedur tersimpan untuk setiap baris yang dimodifikasi dalam DataSet. Untuk mendapatkan informasi selengkapnya, lihat Parameter DataAdapter.

Catatan

Penting untuk memahami perbedaan antara menghapus baris dalam DataTable dan menghapus baris. Ketika Anda memanggil metode Remove atau RemoveAt, baris segera dihapus. Setiap baris yang sesuai di sumber data back end tidak akan terpengaruh jika Anda kemudian meneruskan DataTable atau DataSet ke DataAdapter dan memanggil Update. Ketika Anda menggunakan metode Delete, baris tetap berada di DataTable dan ditandai untuk penghapusan. Kemudian, jika Anda meneruskan DataTable atau DataSet ke DataAdapter dan memanggil Update, baris yang sesuai di sumber data back end akan dihapus.

Jika DataTable Anda memetakan menuju atau dihasilkan dari tabel database tunggal, Anda dapat memanfaatkan objek DbCommandBuilder untuk secara otomatis menghasilkan objek DeleteCommand, InsertCommand, dan UpdateCommand dari DataAdapter. Untuk mendapatkan informasi selengkapnya, lihat Menghasilkan Perintah dengan CommandBuilders.

Menggunakan UpdatedRowSource untuk Memetakan Nilai ke Himpunan Data

Anda dapat mengontrol cara nilai yang dikembalikan dari sumber data dipetakan kembali ke panggilan DataTable berikut ke metode Perbarui dari DataAdapter, dengan menggunakan properti UpdatedRowSource objek DbCommand. Dengan mengatur properti UpdatedRowSource ke salah satu nilai enumerasi UpdateRowSource, Anda dapat mengontrol apakah parameter output yang dikembalikan oleh perintah DataAdapter diabaikan atau diterapkan ke baris yang diubah di DataSet. Anda juga dapat menentukan apakah baris pertama yang dikembalikan (jika ada) diterapkan ke baris yang diubah di DataTable.

Tabel berikut menjelaskan nilai enumerasi UpdateRowSource yang berbeda dan pengaruhnya terhadap perilaku perintah yang digunakan dengan DataAdapter.

Enumerasi UpdatedRowSource Deskripsi
Both Parameter output dan baris pertama dari kumpulan hasil yang dikembalikan dapat dipetakan ke baris yang diubah di DataSet.
FirstReturnedRecord Hanya data di baris pertama dari kumpulan hasil yang dikembalikan yang dapat dipetakan ke baris yang diubah di DataSet.
None Setiap parameter output atau baris dari kumpulan hasil yang dikembalikan diabaikan.
OutputParameters Hanya parameter output yang dapat dipetakan ke baris yang diubah di DataSet.

Metode Update ini menyelesaikan perubahan Anda kembali ke sumber data; namun, klien lain mungkin telah memodifikasi data di sumber data sejak terakhir kali Anda mengisi DataSet. Untuk melakukan refresh DataSet Anda dengan data saat ini, gunakan metode DataAdapter dan Fill. Baris baru akan ditambahkan ke tabel, dan informasi yang diperbarui akan dimasukkan ke dalam baris yang sudah ada. Metode Fill menentukan apakah baris baru akan ditambahkan atau baris yang ada akan diperbarui dengan memeriksa nilai kunci primer baris di DataSet dan baris yang dikembalikan oleh SelectCommand. Jika metode Fill menemukan nilai kunci primer untuk baris di DataSet yang cocok dengan nilai kunci primer dari baris dalam hasil yang dikembalikan oleh SelectCommand, metode memperbarui baris yang ada dengan informasi dari baris yang dikembalikan oleh SelectCommand dan mengatur RowState dari baris yang ada ke Unchanged. Jika baris yang dikembalikan oleh SelectCommand memiliki nilai kunci primer yang tidak cocok dengan salah satu nilai kunci primer baris dalam DataSet, metode Fill menambahkan baris baru dengan RowState dari Unchanged.

Catatan

Jika SelectCommand mengembalikan hasil GABUNGAN LUAR, DataAdapter tidak akan mengatur nilai PrimaryKey untuk menghasilkan DataTable. Anda harus menentukan PrimaryKey diri Anda untuk memastikan bahwa baris duplikat diselesaikan dengan benar. Untuk informasi selengkapnya, lihat Menentukan Kunci Utama.

Untuk menangani pengecualian yang dapat terjadi ketika memanggil metode Update, Anda dapat menggunakan peristiwa RowUpdated untuk merespons kesalahan pembaruan baris ketika terjadi (lihat Menangani Peristiwa DataAdapter), atau Anda dapat mengatur DataAdapter.ContinueUpdateOnError ke true sebelum memanggil Update, dan merespons informasi kesalahan yang disimpan di properti RowError baris tertentu ketika pembaruan selesai (lihat Informasi Kesalahan Baris).

Catatan

Memanggil AcceptChanges pada DataSet, DataTable, atau DataRow akan menyebabkan semua nilai Original untuk DataRow ditimpa dengan nilai Current untuk DataRow. Jika nilai kolom yang mengidentifikasi baris sebagai unik telah dimodifikasi, setelah memanggil AcceptChanges nilai Original tidak akan lagi cocok dengan nilai di sumber data. AcceptChanges dipanggil secara otomatis untuk setiap baris selama panggilan ke metode Perbarui dari DataAdapter. Anda dapat mempertahankan nilai asli selama panggilan ke metode Perbarui dengan terlebih dahulu mengatur properti AcceptChangesDuringUpdate dari DataAdapter ke false, atau dengan membuat penanganan aktivitas untuk peristiwa RowUpdated dan mengatur Status ke SkipCurrentRow. Untuk mendapatkan informasi selengkapnya, lihat Menggabungkan Konten Himpunan Data dan Menangani Peristiwa DataAdapter.

Contoh

Contoh berikut menunjukkan cara melakukan pembaruan pada baris yang dimodifikasi dengan secara eksplisit mengatur UpdateCommand dari DataAdapter dan memanggil metode Update. Perhatikan bahwa parameter yang ditentukan dalam klausul WHERE dari pernyataan PERBARUI diatur untuk menggunakan nilai Original dari SourceColumn. Ini penting, karena nilai Current mungkin telah dimodifikasi dan mungkin tidak cocok dengan nilai di sumber data. Nilai Original adalah nilai yang digunakan untuk mengisi DataTable dari sumber data.

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

Kolom AutoIncrement

Jika tabel dari sumber data Anda memiliki kolom kenaikan otomatis, Anda dapat mengisi kolom di DataSet Anda baik dengan mengembalikan nilai kenaikan otomatis sebagai parameter output dari prosedur tersimpan dan memetakannya ke kolom dalam tabel, dengan mengembalikan nilai kenaikan otomatis di baris pertama dari kumpulan hasil yang dikembalikan melalui prosedur tersimpan atau pernyataan SQL, atau dengan menggunakan peristiwa RowUpdated dari DataAdapter untuk menjalankan pernyataan SELECT tambahan. Untuk mendapatkan informasi selengkapnya, lihat Mengambil Nilai Identitas atau Jumlah Otomatis.

Urutan Sisipkan, Perbarui, dan Hapus

Dalam banyak keadaan, urutan perubahan yang dilakukan melalui DataSet dikirimkan ke sumber data adalah penting. Contohnya, jika nilai kunci primer untuk baris yang ada diperbarui, dan baris baru telah ditambahkan dengan nilai kunci primer baru sebagai kunci asing, penting untuk memproses pembaruan sebelum penyisipan.

Anda dapat menggunakan metode Select dari DataTable untuk mengembalikan array DataRow yang hanya mereferensikan baris dengan RowState. Kemudian, Anda dapat meneruskan array yang dikembalikan DataRow ke metode Update dari DataAdapter untuk memproses baris yang dimodifikasi. Dengan menentukan subset baris yang akan diperbarui, Anda dapat mengontrol urutan tempat sisipkan, perbarui, dan hapus diproses.

Contohnya, kode berikut memastikan bahwa baris tabel yang dihapus diproses terlebih dahulu, lalu baris yang diperbarui, lalu baris yang disisipkan.

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

Menggunakan DataAdapter untuk Mengambil dan Memperbarui Data

Anda dapat menggunakan DataAdapter untuk mengambil dan memperbarui data.

  • Sampel menggunakan DataAdapter.AcceptChangesDuringFill untuk mengkloning data dalam database. Jika properti diatur sebagai false, AcceptChanges tidak dipanggil ketika mengisi tabel, dan baris yang baru ditambahkan diperlakukan sebagai baris yang disisipkan. Jadi, sampel menggunakan baris ini untuk menyisipkan baris baru ke dalam database.

  • Sampel menggunakan DataAdapter.TableMappings untuk menentukan pemetaan antara tabel sumber dan DataTable.

  • Sampel menggunakan DataAdapter.FillLoadOption untuk menentukan cara adapter mengisi DataTable dari DbDataReader. Ketika membuat DataTable, Anda hanya dapat menulis data dari database ke versi saat ini atau versi asli dengan mengatur properti sebagai LoadOption.Upsert atau LoadOption.PreserveChanges.

  • Sampel juga akan memperbarui tabel dengan menggunakan DbDataAdapter.UpdateBatchSize untuk melakukan operasi batch.

Sebelum mengompilasi dan menjalankan sampel, Anda perlu membuat database sampel:

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

Lihat juga