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