Freigeben über


Aktualisieren von Datenquellen mit DataAdapters

Gilt für: .NET Framework .NET .NET Standard

Herunterladen von ADO.NET

Zum Aktualisieren von Datenquellen mit den Änderungen, die an einem Update vorgenommen wurden, wird die DataAdapter-Methode des DataSet aufgerufen. Als Argumente akzeptiert die Update-Methode, genau wie die Fill-Methode, eine Instanz eines DataSet sowie ein optionales DataTable-Objekt oder einen DataTable-Namen. Die DataSet-Instanz ist das DataSet, das die vorgenommenen Änderungen enthält, und der DataTable-Wert gibt die Tabelle an, aus der die Änderungen abgerufen werden sollen. Wenn keine DataTable angegeben ist, wird die erste DataTable im DataSet verwendet.

Wenn die Update-Methode aufgerufen wird, analysiert der DataAdapter die vorgenommenen Änderungen und führt dann den entsprechenden Befehl (INSERT, UPDATE oder DELETE) aus. Wenn der DataAdapter eine Änderung an einer DataRow feststellt, verwendet er zum Verarbeiten der Änderung den InsertCommand, den UpdateCommand oder den DeleteCommand.

Diese Eigenschaften ermöglichen es Ihnen, die Leistung Ihrer ADO.NET-Anwendung zu maximieren, indem Sie die Befehlssyntax zur Entwurfszeit festlegen und nach Möglichkeit gespeicherte Prozeduren verwenden. Sie müssen die Befehle vor dem Aufrufen von Update explizit festlegen. Wenn Update aufgerufen wird und der entsprechende Befehl für ein bestimmtes Update nicht vorhanden ist (wenn z. B. DeleteCommand für gelöschte Zeilen fehlt), wird eine Ausnahme ausgelöst.

Wichtig

Wenn Sie gespeicherte SQL Server-Prozeduren verwenden, um Daten mithilfe einer DataAdapter-Klasse zu bearbeiten oder zu löschen, stellen Sie sicher, dass Sie in der Definition der gespeicherten Prozedur nicht SET NOCOUNT ON verwenden. Anderenfalls ist die zurückgegebene Anzahl der betroffenen Zeilen gleich Null (0), was der DataAdapter als Parallelitätskonflikt interpretiert. In diesem Fall wird eine DBConcurrencyException ausgelöst.

Befehlsparameter können verwendet werden, um Eingabe- und Ausgabewerte für eine SQL-Anweisung oder gespeicherte Prozedur für jede geänderte Zeile in einer .DataSet Weitere Informationen finden Sie unter DataAdapter-Parameter.

Hinweis

Wichtig ist dabei, zwischen dem Löschen einer Zeile in einer DataTable und dem Entfernen der Zeile zu unterscheiden. Wenn Sie die Remove-Methode oder die RemoveAt-Methode aufrufen, wird die Zeile sofort entfernt. Die entsprechenden Zeilen in der Back-End-Datenquelle sind nicht betroffen, wenn Sie anschließend die DataTable- oder DataSet-Klasse an eine DataAdapter-Klasse übergeben und Updateaufrufen. Wenn Sie die Methode Delete verwenden, bleibt die Zeile in der DataTable erhalten, wird aber als zu löschen markiert. Wenn Sie dann die DataTable- oder DataSet-Klasse an eine DataAdapter-Klasse übergeben und Update aufrufen, wird die entsprechende Zeile in der Back-End-Datenquelle gelöscht.

Wenn Ihre DataTable einer einzelnen Datenbanktabelle zugeordnet ist oder aus einer einzelnen Datenbanktabelle generiert wurde, können Sie mithilfe des DbCommandBuilder-Objekts automatisch das DeleteCommand-, das InsertCommand- und das UpdateCommand-Objekt für den DataAdapter generieren. Weitere Informationen finden Sie unter Generieren von Befehlen mit CommandBuilder-Klassen.

Verwenden von UpdatedRowSource für die Zuordnung von Werten zu einer DataSet-Klasse

Mithilfe der UpdatedRowSource-Eigenschaft eines SqlCommand-Objekts können Sie steuern, wie nach dem Aufrufen der Update-Methode einer DataAdapter-Klasse die von der Datenquelle zurückgegebenen Werte wieder der DataTable-Klasse zugeordnet werden. Durch Festlegen eines der UpdatedRowSource-Enumerationswerte für die UpdateRowSource-Eigenschaft kann gesteuert werden, ob die von den DataAdapter-Befehlen zurückgegebenen Ausgabeparameter ignoriert oder auf die geänderte Zeile im DataSet angewendet werden. Es kann auch festgelegt werden, ob die erste zurückgegebene Zeile (wenn vorhanden) auf die geänderte Zeile in der DataTable angewendet wird.

In der folgenden Tabelle werden die verschiedenen Werte der UpdateRowSource-Enumeration und deren Auswirkungen auf das Verhalten eines mit einem DataAdapter verwendeten Befehls beschrieben.

"UpdatedRowSource"-Enumeration BESCHREIBUNG
Both Sowohl die Ausgabeparameter als auch die erste Zeile eines zurückgegebenen Resultset können der geänderten Zeile im DataSet zugeordnet werden.
FirstReturnedRecord Nur die Daten in der ersten Zeile eines zurückgegebenen Resultset können der geänderten Zeile im DataSet zugeordnet werden.
None Alle Ausgabeparameter oder Zeilen eines zurückgegebenen Resultset werden ignoriert.
OutputParameters Der geänderten Zeile im DataSet können nur Ausgabeparameter zugeordnet werden.

Die Update-Methode aktualisiert die Datenquelle mit den vorgenommenen Änderungen. Die Daten in der Datenquelle können aber seit dem letzten Füllen des DataSet durch andere Clients geändert worden sein. Wenn Sie das DataSet mit den aktuellen Daten aktualisieren möchten, verwenden Sie den DataAdapter und die Fill-Methode. Der Tabelle werden neue Zeilen hinzugefügt, und aktualisierte Informationen werden in die vorhandenen Zeilen eingefügt.

Die Fill-Methode überprüft die Primärschlüsselwerte der Zeilen im DataSet und der vom SelectCommand zurückgegebenen Zeilen und bestimmt so, ob eine neue Zeile hinzugefügt oder die bestehende Zeile aktualisiert werden soll. Wenn die Fill-Methode einen Primärschlüsselwert für eine Zeile im DataSet findet, der mit einem Primärschlüsselwert einer Zeile in den vom SelectCommand zurückgegebenen Ergebnissen übereinstimmt, aktualisiert sie die vorhandene Zeile mit den Informationen aus der vom SelectCommand zurückgegebenen Zeile und legt den RowState der vorhandenen Zeile auf Unchanged fest. Wenn der Primärschlüsselwert einer vom SelectCommand zurückgegebenen Zeile keinem der Primärschlüsselwerte der Zeilen im DataSet entspricht, fügt die Fill-Methode eine neue Zeile mit dem RowStateUnchanged hinzu.

Hinweis

Wenn die SelectCommand-Eigenschaft die Ergebnisse eines OUTER JOIN-Vorgangs zurückgibt, legt die DataAdapter-Klasse für die resultierende DataTable-Klasse keinen PrimaryKey-Wert fest. Sie müssen den PrimaryKey selbst definieren, um sicherzustellen, dass doppelte Zeilen ordnungsgemäß aufgelöst werden.

Zur Behandlung von Ausnahmen, die beim Aufrufen der Update-Methode auftreten können, können Sie das RowUpdated-Ereignis verwenden, um auf beim Aktualisieren von Zeilen auftretende Fehler zu reagieren (siehe Umgang mit DataAdapter-Ereignissen). Alternativ können Sie true für ContinueUpdateOnError festlegen, bevor Sie Update aufrufen, und auf die in der RowError-Eigenschaft einer bestimmten Zeile gespeicherten Fehlerinformationen reagieren, wenn das Update abgeschlossen ist.

Hinweis

Wenn AcceptChanges für die DataSet-, DataTable- oder DataRow-Klasse aufgerufen wird, werden alle Original-Werte einer DataRow-Klasse mit den Current-Werten für die DataRow-Klasse überschrieben. Wenn die Feldwerte, mit denen die Zeile als eindeutig identifiziert wird, geändert wurden, stimmen die AcceptChanges-Werte nicht mehr mit den Werten in der Datenquelle überein, nachdem Original aufgerufen wurde. Während eines Aufrufs der Update-Methode einer DataAdapter-Klasse wird AcceptChanges automatisch für jede Zeile aufgerufen. Sie können die Originalwerte während eines Aufrufs der Update-Methode beibehalten, indem Sie zuerst die -Eigenschaft des auf false setzen oder indem Sie einen Ereignishandler für das -Ereignis erstellen und den auf festlegen. Weitere Informationen finden Sie unter Umgang mit DataAdapter-Ereignissen.

Die folgenden Beispiele zeigen, wie geänderte Zeilen aktualisiert werden können, indem die UpdateCommand-Eigenschaft einer DataAdapter-Klasse explizit festgelegt und deren Update-Methode aufgerufen wird.

Hinweis

Der in der WHERE clause der UPDATE statement angegebene Parameter wird so festgelegt, dass der Original-Wert der SourceColumn-Eigenschaft verwendet wird. Dies ist wichtig, weil der Current-Wert möglicherweise geändert wurde und u. U. nicht mehr mit dem Wert in der Datenquelle übereinstimmt. Beim Original-Wert handelt es sich um den Wert, mit dem die DataTable aus der Datenquelle aufgefüllt wurde.

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

AutoIncrement-Spalten

Wenn die Tabellen aus der Datenquelle automatisch inkrementierende Spalten besitzen, können Sie die Spalten im DataSet füllen. Geben Sie dazu die automatisch inkrementierenden Werte als Ausgabeparameter einer gespeicherten Prozedur zurück, und ordnen Sie diesen Parameter einer Spalte in einer Tabelle zu, indem Sie den automatisch inkrementierenden Wert in der ersten Zeile eines von einer gespeicherten Prozedur oder einer SQL-Anweisung zurückgegebenen Resultset zurückgeben oder indem Sie das RowUpdated-Ereignis des DataAdapter verwenden, um eine weitere SELECT-Anweisung auszuführen. Weitere Informationen und ein Beispiel finden Sie unter Abrufen von Identity- oder Autonumber-Werten.

Sortieren von Einfügungen, Updates und Löschungen

In vielen Fällen ist die Reihenfolge, in der die am DataSet vorgenommenen Änderungen zur Datenquelle gesendet werden, sehr wichtig. Wenn beispielsweise ein Primärschlüsselwert für eine vorhandene Zeile aktualisiert und eine neue Zeile mit dem neuen Primärschlüsselwert als Fremdschlüssel hinzugefügt wurde, muss das Update vor der Einfügung verarbeitet werden.

Mithilfe der Select-Methode der DataTable können Sie ein DataRow-Array zurückgeben, das nur auf Zeilen mit einem bestimmten RowState verweist. Anschließend können Sie das zurückgegebene DataRow-Array an die Update-Methode des DataAdapter übergeben, damit die geänderten Zeilen verarbeitet werden. Wenn Sie eine Teilmenge von Zeilen angeben, die aktualisiert werden sollen, können Sie die Reihenfolge steuern, in der Einfügungen, Updates und Löschvorgänge verarbeitet werden.

Beispiel

Durch den folgenden Code wird beispielsweise sichergestellt, dass die gelöschten Zeilen der Tabelle zuerst verarbeitet werden, anschließend die aktualisierten Zeilen und dann die eingefügten Zeilen.

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

Verwenden einer DataAdapter-Klasse zum Abrufen und Aktualisieren von Daten

Sie können DataAdapter verwenden, um die Daten abzurufen und zu aktualisieren.

  • Im Beispiel wird DataAdapter.AcceptChangesDuringFill verwendet, um die Daten in der Datenbank zu klonen. Wenn die Eigenschaft auf false festgelegt ist, wird AcceptChanges beim Ausfüllen der Tabelle nicht aufgerufen, und die neu hinzugefügten Zeilen werden als eingefügte Zeilen behandelt. Daher werden im Beispiel diese Zeilen zum Einfügen der neuen Zeilen in die Datenbank verwendet.

  • Im Beispiel wird DataAdapter.TableMappings verwendet, um die Zuordnung zwischen der Quelltabelle und der DataTable-Klasse zu definieren.

  • Darüber hinaus wird im Beispiel DataAdapter.FillLoadOption verwendet, um festzulegen, wie der Adapter die DataTable-Klasse aus der DbDataReader-Klasse füllt. Wenn Sie eine DataTable-Klasse erstellen, können Sie die Daten aus der Datenbank nur in die aktuelle Version oder die ursprüngliche Version schreiben, indem Sie die Eigenschaft auf LoadOption.Upsert oder LoadOption.PreserveChanges festlegen.

  • Außerdem wird im Beispiel die Tabelle aktualisiert, indem DbDataAdapter.UpdateBatchSize zum Ausführen von Batchvorgängen verwendet wird.

Bevor Sie dieses Beispiel kompilieren und ausführen, müssen Sie die Beispieldatenbank erstellen:

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

Weitere Informationen