DataAdapter を使用してデータ ソースを更新する

適用対象: .NET Framework .NET .NET Standard

ADO.NET のダウンロード

UpdateDataAdapter メソッドを呼び出して、変更を DataSet からデータ ソースに反映します。 Update メソッドは、Fill メソッドと同様に、引数として DataSet のインスタンスおよびオプションの DataTable オブジェクトまたは DataTable 名を受け取ります。 DataSet のインスタンスは、行われた変更点を格納する DataSet です。DataTable は、変更点の取得元のテーブルです。 DataTable を指定しなかった場合、DataTable 内の最初の DataSet が使用されます。

Update メソッドを呼び出すと、DataAdapter は、既に加えられた変更を解析し、適切なコマンド (INSERT、UPDATE、または DELETE) を実行します。 DataAdapterDataRow へ加えられた変更を検出すると、InsertCommandUpdateCommand、または DeleteCommand を使用してその変更を処理します。

これらのプロパティを使用すると、設計時にコマンド構文を指定し、可能であれば、ストアド プロシージャを介して、ADO.NET アプリケーションのパフォーマンスを最大限に高めることができます。 コマンドは Update を呼び出す前に明示的に設定する必要があります。 Update を呼び出し、その更新に関連する適切なコマンドが存在しない場合 (たとえば、削除済みの行に関連する DeleteCommand が存在しない場合) は、例外がスローされます。

重要

DataAdapter を使用してデータの編集または削除を行うために SQL Server ストアド プロシージャを使用している場合は、ストアド プロシージャの定義内で SET NOCOUNT ON を使用しないようにしてください。 処理された行数がゼロとして返され、DataAdapter によってコンカレンシーの競合として解釈されてしまいます。 この場合、DBConcurrencyException がスローされます。

コマンド パラメーターを使用して、SQL ステートメントまたはストアド プロシージャの入力値と出力値を、変更された行 DataSetごとに指定できます。 詳細については、「DataAdapter パラメーター」を参照してください。

Note

DataTable の行を Delete することと、行を Remove することの違いを理解することが大切です。 Remove メソッドまたは RemoveAt メソッドを呼び出した場合、行は直ちに削除されます。 DataTable または DataSetDataAdapter に渡し、Update を呼び出した場合、バック エンド データ ソース内の対応する行が影響を受けることはありませんDelete メソッドを使用した場合、行はそのまま DataTable 内に維持され、削除対象としてマークされます。 次に、DataTable または DataSetDataAdapter に渡して Updateを呼び出すと、バック エンド データ ソース内の対応する行が削除されます

DataTable を単一データベース テーブルに割り当てたり、単一データベースから生成する場合は、DbCommandBuilder オブジェクトを利用して自動的に DeleteCommandInsertCommand オブジェクト、UpdateCommand オブジェクト、および DataAdapter オブジェクトを生成できます。 詳細については、「CommandBuilder を使用したコマンドの生成」を参照してください。

UpdatedRowSource を使用して DataSet に値をマップする

SqlCommand オブジェクトの UpdatedRowSource プロパティを使用すると、DataAdapterUpdate メソッドの呼び出し後にデータ ソースから返される値を DataTable にマップする方法を制御できます。 UpdatedRowSource プロパティを UpdateRowSource 列挙型の値の 1 つに設定することで、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 によって返された行の情報で既存の行を更新して、既存の行の RowStateUnchanged に設定します。 SelectCommand によって返された行の主キーの値が、DataSet のどの行の主キーの値にも一致しない場合、Fill メソッドは、RowStateUnchanged の新しい行を追加します。

Note

SelectCommand から OUTER JOIN の結果が返される場合、結果として得られる DataTablePrimaryKey 値は DataAdapter によって設定されません。 自分で PrimaryKey を定義して、重複行が正しく反映されるようにする必要があります。

Update メソッドの呼び出し時に発生する可能性がある例外を処理するには、行更新エラーが発生したときに RowUpdated イベントを使用してそれらに応答することも (DataAdapter のイベントの処理に関するページを参照)、Update の呼び出しの前に ContinueUpdateOnErrortrue に設定し、更新が完了した時点で特定の行の RowError プロパティに格納されているエラー情報に応答することもできます。

Note

DataSetDataTable、または DataRow に対して AcceptChanges を呼び出すと、DataRow に対するすべての Original の値が、DataRow に対する Current の値で上書きされます。 行を一意に識別するフィールド値が変更された場合は、AcceptChanges 呼び出しの後に Original 値がデータ ソースの値と一致しなくなります。 AcceptChanges は、DataAdapterUpdate メソッドの呼び出し中に行ごとに自動的に呼び出されます。 Update メソッドの呼び出し中に元の値を維持するには、まず AcceptChangesDuringUpdateDataAdapter プロパティを false に設定するか、RowUpdated イベントのイベント ハンドラーを作成し、その StatusSkipCurrentRow に設定します。 詳細については、「DataAdapter イベントの処理」を参照してください。

次の例では、DataAdapterUpdateCommand を明示的に設定し、その Update メソッドを呼び出すことにより、変更済みの行に対して更新を実行する方法を示します。

Note

UPDATE statementWHERE clause で指定されるパラメーターは、SourceColumnOriginal 値を使用するように設定されます。 Current 値が既に変更されている可能性、そしてデータ ソースの値と一致していない可能性があるため、この設定は重要です。 Original 値は、データ ソースから DataTable にデータを取得するために使用された値です。

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 列

データ ソースから取得したテーブルに自動インクリメント列がある場合、自動インクリメント値をストアド プロシージャの出力パラメーターとして取得してそれをテーブルの列に割り当てるか、ストアド プロシージャまたは SQL ステートメントによって返された結果セットの最初の行の自動インクリメント値を取得するか、または DataSetRowUpdated イベントを使用して追加の SELECT コマンドを実行することによって、DataAdapter の列に値を格納できます。 詳細と例については、「ID 値または autonumber 値の取得」を参照してください。

挿入、更新、削除の順序

通常の条件下では、DataSet を使用して行う変更の順序をデータ ソースに送信することが重要です。 たとえば、既存の行の主キーの値を更新し、その新しい主キーの値を外部キーとして新しい行を追加する場合、更新は挿入の前に処理する必要があります。

SelectDataTable メソッドを使用すると、特定の DataRow を持つ行だけを参照する RowState 配列を返すことができます。 その後で、返された DataRow 配列を UpdateDataAdapter メソッドに渡して変更行を処理できます。 更新する行のサブセットを指定することで、挿入、更新、および削除の処理順序を制御できます。

たとえば次のコードでは、テーブルの削除行を最初に処理し、次に更新行、最後に挿入行を処理します。

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

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

関連項目