Adatforrások frissítése DataAdapters használatával
A Update
metódus DataAdapter a visszaról DataSet az adatforrásra történő módosítások feloldására van meghívva. A Update
metódushoz hasonlóan a Fill
metódus argumentumként egy példányt DataSet
és egy opcionális DataTable objektumot vagy DataTable
nevet vesz fel. A DataSet
példány az, DataSet
amely tartalmazza a végrehajtott módosításokat, és azonosítja azt a DataTable
táblát, amelyből lekérheti a módosításokat. Ha nincs DataTable
megadva, a rendszer az elsőt DataTable
DataSet
használja.
Amikor meghívja a Update
metódust, a DataAdapter
rendszer elemzi a végrehajtott módosításokat, és végrehajtja a megfelelő parancsot (IN Standard kiadás RT, UPDATE vagy DELETE). Amikor a DataAdapter
rendszer módosítást tapasztal, DataRowa módosítást a InsertCommand, UpdateCommandvagy DeleteCommand a módosítás feldolgozására használja. Ez lehetővé teszi a ADO.NET alkalmazás teljesítményének maximalizálását a parancsszintaxis tervezéskor és lehetőség szerint tárolt eljárások használatával történő megadásával. A hívás Update
előtt explicit módon kell beállítania a parancsokat. Ha Update
a rendszer meghívja, és a megfelelő parancs nem létezik egy adott frissítéshez (például nem DeleteCommand
a törölt sorokhoz), a rendszer kivételt okoz.
Feljegyzés
Ha SQL Serverrel tárolt eljárásokat használ adatok szerkesztéséhez vagy törléséhez egy DataAdapter
használatával, győződjön meg arról, hogy a tárolt eljárásdefinícióban nem használja a Standard kiadás T NOCOUNT ON parancsot. Ez azt eredményezi, hogy az érintett sorok száma nulla lesz, amelyet az DataAdapter
egyidejűségütközésként értelmez. Ebben az esetben egy DBConcurrencyException lesz dobva.
A parancsparaméterekkel megadhat bemeneti és kimeneti értékeket egy SQL-utasításhoz vagy tárolt eljáráshoz egy adott sor minden módosított sorához DataSet
. További információ: DataAdapter Parameters.
Feljegyzés
Fontos megérteni a sor törlése és a sor DataTable eltávolítása közötti különbséget. Amikor meghívja a metódust Remove
RemoveAt
, a sor azonnal el lesz távolítva. A háttéradatforrás megfelelő sorait a rendszer nem érinti, ha ezután átadja a DataTable
DataSet
megfelelő sorokat egy vagy több DataAdapter
hívásnak Update
. A metódus használata esetén a Delete
sor a DataTable
törléshez meg lesz jelölve. Ha ezután átadja a DataTable
vagy DataSet
egy DataAdapter
hívásnak Update
, a rendszer törli a háttéradatforrás megfelelő sorát.
Ha a DataTable
leképezések egyetlen adatbázistáblára kerülnek vagy abból jönnek létre, az objektumot kihasználva DbCommandBuilder automatikusan létrehozhatja a DeleteCommand
, InsertCommand
és UpdateCommand
az objektumokat a DataAdapter
. További információ: Parancsok generálása commandbuilderekkel.
Az UpdatedRowSource használata az értékek adatkészletre való leképezéséhez
Egy objektum tulajdonságával szabályozhatja, hogy az adatforrásból visszaadott értékek hogyan legyenek visszaképezve egy DataTable
objektum frissítési metódusának DataAdapter
UpdatedRowSource következő hívásáraDbCommand. Ha a UpdatedRowSource
tulajdonságot az UpdateRowSource enumerálási értékek egyikére állítja, szabályozhatja, hogy a DataAdapter
parancsok által visszaadott kimeneti paraméterek figyelmen kívül lesznek-e hagyva, vagy alkalmazva vannak-e a módosított sorra a DataSet
parancsokban. Azt is megadhatja, hogy az első visszaadott sor (ha létezik) alkalmazva legyen-e a módosított sorra a DataTable
.
Az alábbi táblázat az enumerálás különböző értékeit UpdateRowSource
ismerteti, és azt, hogy ezek hogyan befolyásolják a parancsok viselkedését.DataAdapter
UpdatedRowSource Enumeration | Leírás |
---|---|
Both | A kimeneti paraméterek és a visszaadott eredményhalmaz első sora is megfeleltethető a módosított sorra a DataSet . |
FirstReturnedRecord | Csak a visszaadott eredményhalmaz első sorában lévő adatok képezhetők le a módosított sorra a DataSet . |
None | A rendszer figyelmen kívül hagyja a visszaadott eredményhalmaz kimeneti paramétereit vagy sorait. |
OutputParameters | Csak a kimeneti paraméterek képezhetők le a módosított sorra a DataSet . |
A Update
módszer feloldja a módosításokat vissza az adatforrásba; más ügyfelek azonban módosíthatják az adatokat az adatforrásban a legutóbbi kitöltés DataSet
óta. Az aktuális adatok frissítéséhez DataSet
használja az és Fill
a metódustDataAdapter
. A rendszer új sorokat ad hozzá a táblához, és a frissített információk a meglévő sorokba lesznek beépítve. A Fill
metódus azt határozza meg, hogy új sor lesz-e hozzáadva, vagy egy meglévő sor frissül-e a sorok DataSet
SelectCommand
elsődleges kulcsértékeinek vizsgálatával. Ha a Fill
metódus egy olyan sor DataSet
elsődleges kulcsértékével találkozik, amely megfelel a visszaadott SelectCommand
eredmények egyik sorából származó elsődleges kulcsértéknek, frissíti a meglévő sort az általa SelectCommand
visszaadott sor adataival, és a RowState meglévő sor értékét a következőre Unchanged
állítja. Ha a SelectCommand
visszaadott sor elsődleges kulcsértéke nem egyezik meg a sorok DataSet
egyik elsődleges kulcsértékével sem, a Fill
metódus egy új sort ad hozzá a következővel RowState
Unchanged
:
Feljegyzés
Ha a SelectCommand
külső illesztés eredményeit adja vissza, a DataAdapter
rendszer nem állít be PrimaryKey
értéket az eredményül kapotthoz DataTable
. Az ismétlődő sorok helyes feloldásához meg kell határoznia PrimaryKey
saját magát. További információ: Elsődleges kulcsok definiálása.
A metódus meghívásakor Update
esetlegesen előforduló kivételek kezeléséhez az esemény használatával RowUpdated
válaszolhat a sorfrissítési hibákra azok bekövetkezésekor (lásd: DataAdapter-események kezelése), vagy beállíthatja true
DataAdapter.ContinueUpdateOnError
a hívás Update
előtt, és válaszolhat egy adott sor tulajdonságában RowError
tárolt hibainformációkra a frissítés befejezésekor (lásd a sorhiba adatait).
Feljegyzés
A hívás AcceptChanges
hatására a rendszer DataTable
felülírja az DataSet
adott Original
értékeketDataRow
, vagy DataRow
felülírja a Current
függvény értékét.DataRow
Ha a sort egyediként azonosító mezőértékek módosultak, az értékek meghívása AcceptChanges
után az Original
értékek már nem egyeznek az adatforrás értékeivel. AcceptChanges
a rendszer automatikusan meghívja az egyes sorokat az adott DataAdapter
sor frissítési metódusának hívása során. Az eredeti értékeket megőrizheti az Update metódus hívása során úgy, hogy először hamisra állítja a AcceptChangesDuringUpdate
DataAdapter
tulajdonságot, vagy létrehoz egy eseménykezelőt az RowUpdated
eseményhez, és a következőre SkipCurrentRowállítja az Status értéket. További információ: Adathalmaz tartalmának egyesítése és DataAdapter-események kezelése.
Példa
Az alábbi példák bemutatják, hogyan hajthat végre módosításokat a módosított sorokon a UpdateCommand
metódus explicit beállításával DataAdapter
és meghívásával Update
. Figyelje meg, hogy az UPDATE utasítás WHERE záradékában megadott paraméter a Original
SourceColumn
. Ez azért fontos, mert lehetséges, hogy az Current
érték módosult, és lehet, hogy nem egyezik az adatforrás értékével. Az Original
érték az az érték, amelyet az DataTable
adatforrásból való feltöltéshez használtak.
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
Automatikus beírási oszlopok
Ha az adatforrás táblái automatikusan növekményes oszlopokkal rendelkeznek, az automatikus DataSet
növekményes értéket egy tárolt eljárás kimeneti paramétereként adja vissza, és megfelelteti azt egy tábla egyik oszlopának, ha egy tárolt eljárás vagy SQL-utasítás által visszaadott eredményhalmaz első sorában adja vissza az automatikus növekmény értékét. vagy az RowUpdated
esemény DataAdapter
használatával egy további Standard kiadás LECT utasítást hajthat végre. További információ és példa: Identitás vagy számértékek beolvasása.
Beszúrások, Frissítések és törlések sorrendje
Sok esetben fontos, hogy a módosítások DataSet
milyen sorrendben legyenek elküldve az adatforrásnak. Ha például egy meglévő sor elsődleges kulcsértéke frissül, és egy új sor lett hozzáadva az új elsődleges kulcs értékével idegen kulcsként, fontos a frissítés feldolgozása a beszúrás előtt.
A metódussal Select
DataTable
olyan tömböt adhat vissza DataRow
, amely csak egy adott RowState
sorra hivatkozik. Ezután átadhatja a visszaadott DataRow
tömböt a Update
módosított sorok feldolgozásának metódusának DataAdapter
. A frissíteni kívánt sorok egy részhalmazának megadásával szabályozhatja a beszúrások, frissítések és törlések feldolgozásának sorrendjét.
A következő kód például biztosítja, hogy a tábla törölt sorait előbb feldolgozzák, majd a frissített sorokat, majd a beszúrt sorokat.
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));
Adatok lekérése és frissítése DataAdapter használatával
A DataAdapter használatával lekérheti és frissítheti az adatokat.
A minta a DataAdapter.AcceptChangesDuringFill használatával klónozza az adatokat az adatbázisban. Ha a tulajdonság értéke hamis, az AcceptChanges nem lesz meghívva a táblázat kitöltésekor, és az újonnan hozzáadott sorok beszúrt sorokként lesznek kezelve. A minta tehát ezekkel a sorokkal szúrja be az új sorokat az adatbázisba.
A minták a DataAdapter.TableMappings használatával határozzák meg a forrástábla és a DataTable közötti leképezést.
A minta a DataAdapter.FillLoadOption használatával határozza meg, hogy az adapter hogyan tölti ki a DataTable-t a DbDataReaderből. DataTable létrehozásakor az adatbázis adatait csak az aktuális vagy az eredeti verzióra írhatja úgy, hogy a tulajdonságot LoadOption.Upsert vagy LoadOption.PreserveChanges értékként állítja be.
A minta a dbDataAdapter.UpdateBatchSize használatával is frissíti a táblát a kötegműveletek végrehajtásához.
A minta fordítása és futtatása előtt létre kell hoznia a mintaadatbázist:
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()]
[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);
}
}
}