Memperbarui sumber data dengan DataAdapters
Berlaku untuk: .NET Framework .NET .NET Standard
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.
Properti ini memungkinkan Anda memaksimalkan performa aplikasi ADO.NET Anda dengan menentukan sintaks perintah pada waktu desain dan, jika 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.
Penting
Jika Anda menggunakan prosedur tersimpan SQL Server untuk mengedit atau menghapus data menggunakan DataAdapter
, pastikan 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 DataSet
dimodifikasi dalam . Untuk 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. Jika Anda kemudian 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 informasi selengkapnya, lihat Membuat perintah dengan CommandBuilders.
Menggunakan UpdatedRowSource untuk memetakan nilai ke Himpunan Data
Anda dapat mengontrol bagaimana nilai yang dikembalikan dari sumber data dipetakan kembali ke DataTable
panggilan berikut ke Update metode DataAdapter
, dengan menggunakan UpdatedRowSource properti SqlCommand objek. 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
SelectCommand
Jika mengembalikan hasil OUTER JOIN, DataAdapter
tidak akan menetapkan PrimaryKey
nilai untuk yang dihasilkan DataTable
. Anda harus menentukan PrimaryKey
diri Anda untuk memastikan bahwa baris duplikat diselesaikan dengan benar.
Untuk menangani pengecualian yang mungkin terjadi saat memanggil Update
metode, Anda dapat menggunakan RowUpdated
peristiwa untuk merespons kesalahan pembaruan baris saat terjadi (lihat Menangani peristiwa DataAdapter), atau Anda dapat mengatur ContinueUpdateOnError ke true
sebelum memanggil Update
, dan merespons informasi kesalahan yang disimpan di RowError
properti baris tertentu saat pembaruan selesai.
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 Update
metode 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 informasi selengkapnya, lihat Menangani Peristiwa DataAdapter.
Contoh berikut menunjukkan cara melakukan pembaruan pada baris yang dimodifikasi dengan secara eksplisit mengatur UpdateCommand
dari DataAdapter
dan memanggil metode Update
.
Catatan
Parameter yang ditentukan dalam WHERE clause
diatur UPDATE statement
untuk menggunakan Original
nilai 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.
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]);
}
}
}
}
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 informasi selengkapnya dan contohnya, lihat Mengambil nilai identitas atau autonumber.
Urutan sisipan, pembaruan, dan penghapusan
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.
Contoh
Contohnya, kode berikut memastikan bahwa baris tabel yang dihapus diproses terlebih dahulu, lalu baris yang diperbarui, lalu baris yang disisipkan.
// 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));
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 saat 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 bagaimana adaptor mengisi DataTable dari DbDataReader. Saat 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 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"]));
}
}