DataAdapter로 데이터 원본 업데이트
Update
의 DataAdapter 메서드를 호출하면 DataSet의 변경 내용이 데이터 소스에 다시 적용됩니다. Update
메서드는 Fill
메서드와 마찬가지로 DataSet
의 인스턴스, 선택적 DataTable 개체 또는 DataTable
이름을 인수로 사용합니다. DataSet
인스턴스는 변경 내용을 포함하는 DataSet
이며 DataTable
은 변경 내용을 검색할 테이블을 식별합니다. DataTable
을 지정하지 않으면 DataTable
의 첫 번째 DataSet
이 사용됩니다.
Update
메서드를 호출하면 DataAdapter
가 변경 내용을 분석하여 INSERT, UPDATE, DELETE 등의 적절한 명령을 실행합니다. DataAdapter
에 대한 변경 사항이 발견되면 DataRow는 InsertCommand, UpdateCommand 또는 DeleteCommand를 사용하여 변경 내용을 처리합니다. 이로써 가능한 경우 디자인 타임에 저장 프로시저를 사용하여 명령 구문을 지정함으로써 ADO.NET 애플리케이션의 성능을 최대화할 수 있습니다. Update
를 호출하기 전에 명령을 명시적으로 설정해야 합니다. Update
를 호출했는데 삭제된 행에 대한 DeleteCommand
가 없는 경우와 같이 특정 업데이트에 사용할 적절한 명령이 없으면 예외가 throw됩니다.
참고 항목
SQL Server 저장 프로시저를 사용하여 DataAdapter
를 사용하여 데이터를 편집하거나 삭제하는 경우 저장 프로시저 정의에서 SET NOCOUNT ON을 사용하지 않아야 합니다. 이로 인해 영향을 받은 행 수가 0으로 반환되고, DataAdapter
는 이를 동시성 충돌로 해석합니다. 이 이벤트에서는 DBConcurrencyException이 throw됩니다.
명령 매개 변수를 사용하여 DataSet
의 수정된 각 행에 대해 SQL 문이나 저장 프로시저의 입력 및 출력 값을 지정할 수 있습니다. 자세한 내용은 DataAdapter 매개 변수를 참조하세요.
참고 항목
DataTable에서의 행 삭제 및 제거의 차이점을 이해하는 것이 중요합니다. Remove
또는 RemoveAt
메서드를 호출하면 행이 즉시 제거됩니다. 그런 다음 DataTable
에 DataSet
또는 DataAdapter
을 전달하고 Update
를 호출하면 백 엔드 데이터 소스에 있는 해당 행은 아무런 영향을 받지 않습니다. Delete
메서드를 사용하면 행이 DataTable
에 그대로 남아 있고 삭제 표시됩니다. 그런 다음 DataTable
에 DataSet
또는 DataAdapter
을 전달하고 Update
를 호출하면 백 엔드 데이터 소스의 해당 행이 삭제됩니다.
DataTable
이 단일 데이터베이스 테이블에 매핑되거나 단일 데이터베이스 테이블에서 생성된 경우에는 DbCommandBuilder 개체를 사용하여 DeleteCommand
의 InsertCommand
, UpdateCommand
및 DataAdapter
개체를 자동으로 생성할 수 있습니다. 자세한 내용은 참조 commandbuilder를 사용 하를 사용 하 여 명령 생성합니다.
UpdatedRowSource를 사용하여 DataSet에 값 매핑
DataTable
개체의 DataAdapter
속성을 사용하면 UpdatedRowSource의 Update 메서드를 호출한 이후 데이터 소스에서 반환된 값이 DbCommand에 다시 매핑되는 방법을 제어할 수 있습니다. UpdatedRowSource
속성을 UpdateRowSource 열거형 값 중 하나로 설정하면 DataAdapter
명령에서 반환하는 출력 매개 변수를 무시할지 아니면 DataSet
의 변경된 행에 적용할지 제어할 수 있습니다. 반환되는 첫 번째 행이 있는 경우 이를 DataTable
의 변경된 행에 적용할지 여부도 지정할 수 있습니다.
다음 표에서는 UpdateRowSource
열거형의 여러 값과 각 값이 DataAdapter
에 사용하는 명령의 동작에 미치는 영향에 대해 설명합니다.
UpdatedRowSource 열거형 | 설명 |
---|---|
Both | 출력 매개 변수 및 반환된 결과 집합의 첫 번째 행 모두 DataSet 의 변경된 행에 매핑할 수 있습니다. |
FirstReturnedRecord | 반환된 결과 집합의 첫 번째 행 데이터만 DataSet 의 변경된 행에 매핑할 수 있습니다. |
None | 출력 매개 변수 또는 반환된 결과 집합의 행이 모두 무시됩니다. |
OutputParameters | 출력 매개 변수만 DataSet 의 변경된 행에 매핑할 수 있습니다. |
Update
메서드는 변경 내용을 데이터 소스에 다시 적용하지만 사용자가 DataSet
을 마지막으로 채운 이후에 다른 클라이언트에서 데이터 소스의 데이터를 수정했을 수도 있습니다. 현재 데이터로 DataSet
을 새로 고치려면 DataAdapter
및 Fill
메서드를 사용합니다. 그러면 새 행이 테이블에 추가되고 업데이트된 정보가 기존 행에 통합됩니다. Fill
메서드는 DataSet
에 있는 행과 SelectCommand
에서 반환된 행의 기본 키 값을 검사하여 새 행을 추가할지 또는 기존 행을 업데이트할지 결정합니다. Fill
가 반환한 결과 행의 기본 키 값과 일치하는 DataSet
행의 기본 키 값이 발견되면 SelectCommand
메서드는 기존 행을 SelectCommand
가 반환한 행의 정보로 업데이트하고 기존 행의 RowState를 Unchanged
로 설정합니다 SelectCommand
가 반환한 행의 기본 키 값이 DataSet
행의 기본 키 값과 일치하지 않으면 Fill
메서드는 RowState
가 Unchanged
인 새 행을 추가합니다.
참고 항목
SelectCommand
가 OUTER JOIN의 결과를 반환하면 DataAdapter
는 결과 PrimaryKey
에 대해 DataTable
값을 설정하지 않습니다. 행 중복 문제를 해결하려면 PrimaryKey
를 직접 정의해야 합니다. 자세한 내용은 기본 키 정의합니다.
Update
메서드를 호출할 때 발생할 수 있는 예외를 처리하려면 행 업데이트 오류가 발생했을 때 RowUpdated
이벤트를 사용하여 응답하거나(DataAdapter 이벤트 처리 참조), DataAdapter.ContinueUpdateOnError
를 호출하기 전에 true
를 Update
로 설정하고 업데이트가 완료되면 특정 행의 RowError
속성에 저장된 오류 정보에 응답합니다(행 오류 정보 참조).
참고 항목
DataSet
, DataTable
또는 DataRow
에 대해 AcceptChanges
를 호출하면 DataRow
의 Current
값이 DataRow
의 모든 Original
값을 덮어씁니다. 행을 고유하게 식별하는 필드 값을 수정한 경우 AcceptChanges
를 호출하면 Original
값이 데이터 소스의 값과 더 이상 일치하지 않습니다. AcceptChanges
는 DataAdapter
의 Update 메서드를 호출하는 동안 각 행에 대해 자동으로 호출됩니다. 먼저 AcceptChangesDuringUpdate
의 DataAdapter
속성을 false로 설정하거나 RowUpdated
이벤트에 대한 이벤트 처리기를 만들고 Status를 SkipCurrentRow로 설정하면 Update 메서드를 호출할 때 원래 값을 보존할 수 있습니다. 자세한 내용은 DataSet 콘텐츠 병합 및 DataAdapter 이벤트 처리를 참조하세요.
예시
다음 예제에서는 DataAdapter
의 UpdateCommand
를 명시적으로 설정하고 Update
메서드를 호출하여 수정된 행에 대해 업데이트를 수행하는 방법을 보여 줍니다. UPDATE 문의 WHERE 절에 지정된 매개 변수는 Original
의 SourceColumn
값을 사용하도록 설정되어 있습니다. Current
값이 수정되어 데이터 소스에 있는 값과 일치하지 않을 수 있기 때문에 이 설정은 매우 중요합니다. Original
값은 데이터 소스에서 DataTable
을 채우는 데 사용한 값입니다.
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
AutoIncrement 열
데이터 소스의 테이블에 자동 증분 열이 있으면 저장 프로시저의 출력 매개 변수로 자동 증분 값을 반환하여 테이블의 열에 매핑하거나, 저장 프로시저 또는 SQL 문에서 반환된 결과 집합의 첫 번째 행에 있는 자동 증분 값을 반환하거나, DataSet
의 RowUpdated
이벤트를 통해 추가적인 SELECT 문을 실행하여 DataAdapter
의 열을 채울 수 있습니다. 자세한 내용과 예는 ID 또는 자동 번호 값 검색을 참조하세요.
삽입, 업데이트 및 삭제 순서
대부분의 경우에는 DataSet
을 통해 변경된 내용이 데이터 소스에 전송되는 순서가 매우 중요합니다. 예를 들어 기존 행의 기본 키 값을 업데이트하고 새 기본 키 값이 외래 키로 지정된 새 행을 추가하는 경우에는 삽입하기 전에 업데이트를 처리해야 합니다.
Select
의 DataTable
메서드를 사용하여 특정 DataRow
의 행만 참조하는 RowState
배열을 반환할 수 있습니다. 그런 다음 반환된 DataRow
배열을 Update
의 DataAdapter
메서드에 전달하여 수정된 행을 처리할 수 있습니다. 업데이트될 행의 하위 집합을 지정하여 삽입, 업데이트 및 삭제가 처리되는 순서를 제어할 수 있습니다.
예를 들어, 다음 코드에서는 테이블의 삭제된 행이 먼저 처리되고 업데이트된 행과 삽입된 행이 차례로 처리되도록 합니다.
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));
DataAdapter를 사용하여 데이터 검색 및 업데이트
DataAdapter를 사용하여 데이터를 검색하고 업데이트할 수 있습니다.
이 샘플에서는 DataAdapter.AcceptChangesDuringFill을 사용하여 데이터베이스의 데이터를 복제합니다. 이 속성이 false로 설정되어 있으면 테이블을 채울 때 AcceptChanges가 호출되지 않으며 새로 추가된 행은 삽입된 행으로 처리됩니다. 따라서 이 샘플에서는 이러한 행을 사용하여 새 행을 데이터베이스에 삽입합니다.
이 샘플에서는 DataAdapter.TableMappings를 사용하여 소스 테이블과 DataTable 간의 매핑을 정의합니다.
이 샘플에서는 DataAdapter.FillLoadOption을 사용하여 어댑터가 DbDataReader에서 DataTable을 채우는 방법을 결정합니다. DataTable을 만드는 경우 이 속성을 LoadOption.Upsert 또는 LoadOption.PreserveChanges로 설정하여 현재 버전이나 원래 버전에 데이터베이스의 데이터를 쓸 수만 있습니다.
또한 이 샘플에서는 DbDataAdapter.UpdateBatchSize를 사용하여 배치 작업을 수행하는 방법으로 테이블을 업데이트합니다.
샘플을 컴파일 및 실행하기 전에 다음과 같이 샘플 데이터베이스를 만들어야 합니다.
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);
}
}
}