Megosztás a következőn keresztül:


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 DataTableDataSet 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 Updateelő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 DataAdapterhaszná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 RemoveRemoveAt , a sor azonnal el lesz távolítva. A háttéradatforrás megfelelő sorait a rendszer nem érinti, ha ezután átadja a DataTableDataSet 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 DataAdapterUpdatedRowSource 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 DataSetparancsokban. 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 DataSetSelectCommandelső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 SelectCommanderedmé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 DataSetegyik elsődleges kulcsértékével sem, a Fill metódus egy új sort ad hozzá a következővel RowStateUnchanged:

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 trueDataAdapter.ContinueUpdateOnError a hívás Updateelő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 DataTablefelülírja az DataSetadott 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 DataAdaptersor 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 AcceptChangesDuringUpdateDataAdapter 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 OriginalSourceColumn. 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 SelectDataTable olyan tömböt adhat vissza DataRow , amely csak egy adott RowStatesorra 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);
      }
   }
}

Lásd még