.NET アプリでデータベースを操作する

# はじめに

.NET アプリでデータベースに接続できました。

[.NET アプリでデータベースに接続する #MySQL - Qiita](https://qiita.com/tinymouse/items/ac9a0c798d3f36cf02a3)

接続したデータベースを操作してみます。

以下の環境で開発および動作を確認します。

* Windows 11
* Visual Studio 2022
* .NET Framework 4.5.2 または .NET 8.0

# .NET アプリでデータベースを操作する

## Microsoft SQL Server を用意する

まず、Microsoft SQL Server を用意します。

[SQL Server Express（無料版）を導入する #SQLServer - Qiita](https://qiita.com/shironana/items/c98e38bc85c6f5a9bd3c)

さらに、サンプルデータを導入しておきましょう。

[SQL Server:サンプルデータベース「Northwind」を作成する方法 | SE Life Log](https://selifelog.com/blog-entry-2092.html)

データベース「pub」に接続して操作するので導入しておきます。↑

## Microsoft SQL Server に接続する .NET アプリを作成する

Visual Studio で「新しいプロジェクトの作成」して `Windows フォームアプリ` または `Windows フォームアプリ (.NET Framework）` を選択します。
フレームワークは `.NET Framework 4.5.2` または `.NET 8.0` を選択します。

ボタン（Button）とテキストボックス（TextBox）とデータグリッド（DataGridView）をフォームに配置します。

![説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/767af42b-52f1-013d-6ce3-f8fa71d1fee7.png)

Visual Studio の「Nuget パッケージの管理」を開いて `Microsoft.Data.SqlClient` をインストールします。

## データベースに接続する・・①

`SqlConnection` を使ってデータベースに接続します。

参考：[C#でDBに接続する方法(SQL Server) #SQLServer - Qiita](https://qiita.com/homan05872/items/787909b297f150a4d22e)

```csharp
using Microsoft.Data.SqlClient;

    string conStr = "Server=127.0.0.1; User ID=sa; Password=......; Encrypt=false; Database=pubs;";

    private void button1_Click(object sender, EventArgs e)
    {    
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";
            
            cn.Close();
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```

## DataReader で読込する・・②

`SqlCommand` でクエリを実行して、その結果を `SqlDataReader` で一行ずつ読込します。

参考：[SELECT文でデータを取得する(DataReader)。猫の気ままなC#日記](http://okwakatta.net/code/ado05.html)

```csharp
        前略
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";  // ↑ここまで同じ

            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = "SELECT TOP 5 * FROM titles;";

            SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

            while (dr.Read())  // 一行ずつ読込
            {
                textBox1.AppendText($"{dr[0]} {dr[1]} {dr[4]} {dr[9]}\r\n");
            }

            dr.Close();
            cn.Close();  // ↓ここから同じ
        後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button2_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = "SELECT TOP 5 * FROM titles;";

            SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

            while (dr.Read())  // 一行ずつ読込
            {
                textBox1.AppendText($"{dr[0]} {dr[1]} {dr[4]} {dr[9]}\r\n");
            }

            dr.Close();
            cn.Close();
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

## DataReader で読込する・・③

エラー発生したとき `Close()` できないといけないので、`try ～ finally` を使います。

```csharp
        前略
            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT TOP 5 * FROM titles;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                try
                {
                    while (dr.Read())  // 一行ずつ読込
                    {
                        textBox1.AppendText($"{dr[0]} {dr[1]} {dr[4]} {dr[9]}\r\n");
                    }
                }
                finally
                {
                    dr.Close();
                }
            }
            finally
            {
                cn.Close();
            }
        後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button3_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT TOP 5 * FROM titles;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                try
                {
                    while (dr.Read())  // 一行ずつ読込
                    {
                        textBox1.AppendText($"{dr[0]} {dr[1]} {dr[4]} {dr[9]}\r\n");
                    }
                }
                finally
                {
                    dr.Close();
                }
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

## DataReader で読込して DataTable にセットする・・④

`DataTable` オブジェクトを用意して、`DataReader` で読込した内容をセットします。

参考：[C#　DataTable　使い方 | ひろにもブログ](https://hironimo.com/prog/c-sharp/c-datatable/)

```csharp
            前略
                SqlDataReader dr = cmd.ExecuteReader();  // ↑ここまで同じ

                DataTable dt = new DataTable();  // DataTable を用意

                dt.Columns.Add("title_id");  // 列を追加
                dt.Columns.Add("title");
                dt.Columns.Add("price");
                dt.Columns.Add("pubdate");

                try
                {
                    while (dr.Read())  // 一行ずつ読込
                    {
                        DataRow row = dt.Rows.Add();     // 行を追加
                        row["title_id"] = dr["title_id"];
                        row["title"] = dr["title"];
                        row["price"] = dr["price"];
                        row["pubdate"] = dr["pubdate"];
                    }
                }
            後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button4_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT TOP 5 * FROM titles;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                DataTable dt = new DataTable();  // DataTable を用意

                dt.Columns.Add("title_id");  // 列を追加
                dt.Columns.Add("title");
                dt.Columns.Add("price");
                dt.Columns.Add("pubdate");

                try
                {
                    while (dr.Read())  // 一行ずつ読込
                    {
                        DataRow row = dt.Rows.Add();     // 行を追加
                        row["title_id"] = dr["title_id"];
                        row["title"] = dr["title"];
                        row["price"] = dr["price"];
                        row["pubdate"] = dr["pubdate"];
                    }
                }
                finally
                {
                    dr.Close();
                }
                foreach (DataRow row in dt.Rows)  // 一行ずつ処理
                {
                    textBox1.AppendText($"{row[0]} {row[1]} {row[2]} {row[3]}\r\n");
                }
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

データベースのテーブルは、列ごとにデータ型が指定されています。`DataTable` の列も同様に、`データ型` 属性を持っています。列を追加するときデータ型を指定するのが望ましい。
今回は名前だけ指定しています。デフォルトで `String` 型になるようです。

参考：[列に指定した型をつけてDataTableを作成する | 猫の気ままなC#日記](http://okwakatta.net/code/dst04.html)

## DataTable に DataReader から Load する・・⑤

`DataTable` オブジェクトにデータをセットするのに `Load()` を使ってみます。

上記④は `DataTable` に列を事前に定義していますが、`Load()` はデータをセットするとき列もセットしてくれます。

参考：[DataReaderで取得したデータからDataTableを作成する - チャトラのプログラミング学習帳](https://pg-chatora.hateblo.jp/entry/2015/07/13/175140)

```csharp
            前略
                SqlDataReader dr = cmd.ExecuteReader();  // ↑ここまで同じ

                DataTable dt = new DataTable();  // DataTable を用意

                try
                {
                    dt.Load(dr);  // ループで処理する代わり
                }
            後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button5_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT TOP 5 * FROM titles;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                DataTable dt = new DataTable();  // DataTable を用意

                try
                {
                    dt.Load(dr);  // ループで処理する代わり
                }
                finally
                {
                    dr.Close();
                }
            }
            finally
            {
                cn.Close();
            }
            foreach (DataRow row in dt.Rows)  // 一行ずつ処理
            {
                textBox1.AppendText($"{row[0]} {row[1]} {row[4]} {row[9]}\r\n");
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

`Load()` は `DataTable` に列をセットするとき、列ごとに `データ型` もセットしてくれます。`DataReader` がデータベースからデータを読取するときテーブルの列のデータ型も取得していて、これを参照しているのでしょう。
上記のコードで「price」列は `Decimal` 、「pubdate」列は `DateTime` になっていました。

## DbCommand でデータベースを更新する・・⑥

`DbCommand` の派生クラス `SqlCommand` を使って、データベースを更新するクエリを実行します。

参考：[テーブルを書き換えるSQLの実行 | データベース千夜一夜 - PowerNews連載コラム](https://dev.mescius.jp/support/powernews/column/how_to_database/014/page03.htm)


```csharp
        前略
            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "INSERT INTO sales (stor_id, ord_num, ord_date, qty, payterms, title_id) " +
                    $" VALUES ('8042', '99999999', CAST(CURRENT_TIMESTAMP AS DATE), 9, 'on invoice', 'TC7777');";

                cmd.ExecuteNonQuery();       // クエリを実行
                textBox0.Text = "Executed.";
            }
        後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button6_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "INSERT INTO sales (stor_id, ord_num, ord_date, qty, payterms, title_id) " +
                    $" VALUES ('8042', '99999999', CAST(CURRENT_TIMESTAMP AS DATE), 9, 'on invoice', 'TC7777');";

                cmd.ExecuteNonQuery();        // クエリを実行
                textBox0.Text = "Executed.";
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

データベース「pub」のテーブル「sales」は、フィールド「stor_id」「ord_num」「title_id」が主キーに設定されています。このフィールドが同じ値のデータは複数追加できません。したがって、上記のコードを複数実行すると、キー違反のエラーになります。また、フィールド「stor_id」と「title_id」は、それぞれテーブル「stores」「titles」に参照設定されています。したがって、「stores」「titles」に登録されていない値を「stor_id」「title_id」にセットすると、不整合のエラーになります。下記の⑬や⑰の処理したときも同様です。

## Transaction を使う・・⑦

`SqlTransaction` オブジェクトを用意して、実行時にエラー発生したとき `Rollback()` できるようにしましょう。

参考：[[ADO.NETの基本的なコード：トランザクション処理]@[VB.NETde業務アプリ] ](https://vbnet-appli.blogspot.com/2009/03/adonet_18.html)


```csharp
            前略
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "INSERT INTO sales (stor_id, ord_num, ord_date, qty, payterms, title_id) " +
                    $" VALUES ('8042', '{GetRandomString(8)}', CAST(CURRENT_TIMESTAMP AS DATE), 9, 'on invoice', 'TC7777');";

                SqlTransaction tx = cn.BeginTransaction();  // トランザクションを開始

                try
                {
                    cmd.Transaction = tx;
                    cmd.ExecuteNonQuery();  // クエリを実行

                    tx.Commit();                 // 確定する
                    textBox0.Text = "Executed.";
                }
                catch
                {
                    tx.Rollback();  // 取消する
                    throw;
                }
            後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button7_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "INSERT INTO sales (stor_id, ord_num, ord_date, qty, payterms, title_id) " +
                    $" VALUES ('8042', '{GetRandomString(8)}', CAST(CURRENT_TIMESTAMP AS DATE), 9, 'on invoice', 'TC7777');";

                SqlTransaction tx = cn.BeginTransaction();  // トランザクションを開始

                try
                {
                    cmd.Transaction = tx;
                    cmd.ExecuteNonQuery();  // クエリを実行

                    tx.Commit();                 // 確定する
                    textBox0.Text = "Executed.";
                }
                catch
                {
                    tx.Rollback();  // 取消する
                    throw;
                }
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

## DataTable の代わりに DataGridView に表示する・・⑧

上記④は読込したデータを `DataTable` にセットしましたが、代わりに `DataGridView` にセットします。

参考：[【C#入門】DataGridViewの使い方(行の追加・削除、ソートも解説) | 侍エンジニアブログ](https://www.sejuku.net/blog/58436)

```csharp
            前略
                cmd.CommandText = "SELECT TOP 5 * FROM titles;";  // ↑ここまで同じ

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                dataGridView1.Columns.Clear();  // 必要
                dataGridView1.Rows.Clear();

                dataGridView1.Columns.Add("title_id","title_id");  // 列を追加
                dataGridView1.Columns.Add("title", "title");
                dataGridView1.Columns.Add("price", "price");
                dataGridView1.Columns.Add("pubdate", "pubdate");

                try
                {
                    while (dr.Read())  // 一行ずつ読込
                    {
                        int n = dataGridView1.Rows.Add();            // 行を追加
                        DataGridViewRow row = dataGridView1.Rows[n];
                        row.Cells["title_id"].Value = dr["title_id"];
                        row.Cells["title"].Value = dr["title"];
                        row.Cells["price"].Value = dr["price"];
                        row.Cells["pubdate"].Value = dr["pubdate"];
                    }
                }
            後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button8_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT TOP 5 * FROM titles;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                dataGridView1.Columns.Clear();  // 必要
                dataGridView1.Rows.Clear();

                dataGridView1.Columns.Add("title_id", "title_id");  // 列を追加
                dataGridView1.Columns.Add("title", "title");
                dataGridView1.Columns.Add("price", "price");
                dataGridView1.Columns.Add("pubdate", "pubdate");

                try
                {
                    while (dr.Read())  // 一行ずつ読込
                    {
                        int n = dataGridView1.Rows.Add();            // 行を追加
                        DataGridViewRow row = dataGridView1.Rows[n];
                        row.Cells["title_id"].Value = dr["title_id"];
                        row.Cells["title"].Value = dr["title"];
                        row.Cells["price"].Value = dr["price"];
                        row.Cells["pubdate"].Value = dr["pubdate"];
                    }
                }
                finally
                {
                    dr.Close();
                }
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

`DataTable` は列が `データ型` 属性を持っていましたが、`DataGridView` はセルにセットされた値にデータ型が指定できます。
今回は指定しないで値をセットしています。デフォルトで `Object` 型になるようです。

参考：[C# dataGridViewとキャストと型変換のあれこれ - プログラミング素人のはてなブログ](https://s51517765.hatenadiary.jp/entry/2021/09/06/073000)

## DataTable の内容を DataGridView に表示する・・⑨

データをセットした `DataTable` の内容を `DataGridView` に `DataSource` プロパティを使って表示します。

参考：[C#でのDataGridViewデータバインド完全解説: 効率的なデータ操作方法 | フリーランスチャンス](https://freelancechance.tech/csharp-datagridview-bind/)

```csharp
            前略
                cmd.CommandText = "SELECT * FROM sales;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行
                
                DataTable dt = new DataTable();  // DataTable を用意

                try
                {
                    dt.Load(dr);  // データを読込
                }
                finally
                {
                    dr.Close();
                }               // ↑ここまで同じ
                
                dataGridView1.Columns.Clear();  // 必要
                dataGridView1.Rows.Clear();

                dataGridView1.DataSource = dt;  // ループで処理する代わり
            後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button9_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT * FROM sales;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                DataTable dt = new DataTable();  // DataTable を用意

                try
                {
                    dt.Load(dr);  // データを読込
                }
                finally
                {
                    dr.Close();
                }
            }
            finally
            {
                cn.Close();
            }

            dataGridView1.Columns.Clear();  // 必要
            dataGridView1.Rows.Clear();

            dataGridView1.DataSource = dt;  // ループで処理する代わり
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

`DataReader` でテーブルの列のデータ型を取得して `DataTable` の列に設定され、それが `DataGridView` のセルの値に設定されるようです。
上記のコードで「qty」列は `Int16` 、「ord_date」列は `DateTime` になっていました。

## DataTable の内容を DataGridView に表示する・・⑩

上記⑨の `DataTable` オブジェクトをメソッドの外側で宣言します。データがセットされたこのオブジェクトを、下記⑪など別のメソッドで参照したいからです。

```csharp
    private DataTable dt1 = new DataTable();  // メソッドの外側で宣言

    private void button10_Click(object sender, EventArgs e)
    {
         中略
                cmd.CommandText = "SELECT * FROM sales;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                dt1.Clear();  // 必要

                try
                {
                    dt1.Load(dr);  // データを読込
                }
                finally
                {
                    dr.Close();
                }
                
                dataGridView1.Columns.Clear();  // 必要
                dataGridView1.Rows.Clear();

                dataGridView1.DataSource = dt1;  // ループで処理する代わり
         後略
```

<details><summary>省略しないコード</summary>

```csharp
    private DataTable dt1 = new DataTable();  // メソッドの外側で宣言

    private void button10_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;
                cmd.CommandText = "SELECT * FROM sales;";

                SqlDataReader dr = cmd.ExecuteReader();  // クエリを実行

                dt1.Clear();  // 必要

                try
                {
                    dt1.Load(dr);  // データを読込
                }
                finally
                {
                    dr.Close();
                }

                dataGridView1.Columns.Clear();  // 必要
                dataGridView1.Rows.Clear();

                dataGridView1.DataSource = dt1;  // ループで処理する代わり
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

ここで宣言した「dt1」を「DataGridView1」とバインドするのは、事前に実行してもいいでしょう。

```csharp
    private void Form1_Load(object sender, EventArgs e)
    {
        DataGridView1.DataSource = dt1;
    }

    private void button10_Click(object sender, EventArgs e)
    {
        中略
//              dataGridView1.Columns.Clear();
//              dataGridView1.Rows.Clear();

//              dataGridView1.DataSource = dt1;
```

## DataTable を操作する・・⑪

上記⑩で用意した `DataTable` オブジェクト「dt1」に対して操作します。

まず、行を追加してみます。

参考：[【C#】データテーブルの行の追加の方法 - Fun Coding](https://funcording.hatenablog.com/entry/2023/10/02/000000)

```csharp
        try
        {
            DataRow row = dt1.NewRow();
            row["stor_id"] = "8042";
            row["ord_num"] = "99999999";
            row["ord_date"] = DateTime.Today;
            row["qty"] = 9;
            row["payterms"] = "on invoice";
            row["title_id"] = "TC7777";

            dt1.Rows.Add(row);  // 行を追加
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
```

続いて、指定した行のデータを変更してみます。

参考：[DataTableを編集する。猫の気ままなC#日記](http://okwakatta.net/code/dst09.html)

```csharp
        try
        {
            DataRow row = dt1.Rows[1];  // 2 行目を指定

            row["qty"] = 9;  // 変更する
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
```

続いて、指定した行を削除してみます。

参考：[【C#】データテーブルの行の削除の方法 - Fun Coding](https://funcording.hatenablog.com/entry/2023/10/05/000000)

```csharp
        try
        {
            DataRow row = dt1.Rows[3];  // 4 行目を指定

            row.Delete();  // 行を削除
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
```

`DataGridView` オブジェクトにバインドしているので、操作した結果でグリッドの内容が変化します。
逆に、グリッドで操作して変更すると、`DataTable` オブジェクトの内容が変化します。

アプリのメモリ上で展開されている `DataTable` オブジェクトの内容が変更されただけなので、データベースの内容は変更されていません。

<details><summary>省略しないコード</summary>

```csharp
    private void button11_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            DataRow row = dt1.NewRow();
            row["stor_id"] = "8042";
            row["ord_num"] = "99999999";
            row["ord_date"] = DateTime.Today;
            row["qty"] = 9;
            row["payterms"] = "on invoice";
            row["title_id"] = "TC7777";

            dt1.Rows.Add(row);  // 行を追加
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }

        try
        {
            DataRow row = dt1.Rows[1];

            row["title_id"] = "999999";  // 変更する
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }

        try
        {
            DataRow row = dt1.Rows[3];

            row.Delete();  // 行を削除
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

## DataTable の変更を確認する・・⑫

上記⑩で用意した `DataTable` オブジェクト「dt1」に対して操作します。
行ごとの `RowState` を確認してみます。

参考：[.NET の DataTable の行の編集状態（RowState）の変化についてのまとめ #C# - Qiita](https://qiita.com/yiaowang/items/d7558ee35b355255add9)

```csharp
        foreach (DataRow row in dt1.Rows)  // 一行ずつ処理
        {
            textBox1.AppendText($"{row.RowState}\r\n");  // 行の状態を表示
        }
```

<details><summary>省略しないコード</summary>

```csharp
    private void button12_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        foreach (DataRow row in dt1.Rows)  // 一行ずつ処理
        {
            textBox1.AppendText($"{row.RowState}\r\n");  // 行の状態を表示
        }
    }
```
</details>

## DataTable の変更でデータベースを更新する・・⑬

上記⑩で用意した `DataTable` オブジェクト「dt1」に対して操作します。
上記⑦のコードを応用します。行ごとに `RowState` に応じてクエリを変えて実行します。

```csharp
            前略
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;

                SqlTransaction tx = cn.BeginTransaction();
                cmd.Transaction = tx;

                try
                {
                    foreach (DataRow row in dt1.Rows)  // 一行ずつ処理
                    {
                        switch (row.RowState)  // 行の状態に従って
                        {
                            case DataRowState.Unchanged:  // 変更なし

                                continue;

                            case DataRowState.Modified:  // 変更された

                                cmd.CommandText = "UPDATE sales " +
                                    $" SET ord_date = '{row["ord_date"]}', qty = {row["qty"]}, payterms = '{row["payterms"]}', title_id = '{row["title_id"]}' " +
                                    $" WHERE stor_id = '{row["stor_id"]}' AND ord_num = '{row["ord_num"]}';";
                                break;

                            case DataRowState.Added:  // 追加された

                                cmd.CommandText = "INSERT INTO sales (stor_id, ord_num, ord_date, qty, payterms, title_id) " +
                                    $" VALUES ('{row["stor_id"]}', '{row["ord_num"]}', CAST(CURRENT_TIMESTAMP AS DATE), {row["qty"]}, '{row["payterms"]}', '{row["title_id"]}');";
                                break;

                            case DataRowState.Deleted:  // 削除された

                                cmd.CommandText = "DELETE FROM sales " +
                                    $" WHERE stor_id = '{row["stor_id", DataRowVersion.Original]}' AND ord_num = '{row["ord_num",DataRowVersion.Original]}';";
                                break;
                        }

                        cmd.ExecuteNonQuery();  // クエリを実行
                    }
                    tx.Commit();
                    textBox1.Text = "Executed.";
                }
                catch
                {
                    tx.Rollback();
                    throw;
                }
            後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button13_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
            cn.Open();
            textBox0.Text = "Connected.";

            try
            {
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = cn;

                SqlTransaction tx = cn.BeginTransaction();
                cmd.Transaction = tx;

                try
                {
                    foreach (DataRow row in dt1.Rows)  // 一行ずつ処理
                    {
                        switch (row.RowState)  // 行の状態に従って
                        {
                            case DataRowState.Unchanged:  // 変更なし

                                continue;

                            case DataRowState.Modified:  // 変更された

                                cmd.CommandText = "UPDATE sales " +
                                    $" SET ord_date = '{row["ord_date"]}', qty = {row["qty"]}, payterms = '{row["payterms"]}', title_id = '{row["title_id"]}' " +
                                    $" WHERE stor_id = '{row["stor_id"]}' AND ord_num = '{row["ord_num"]}';";
                                break;

                            case DataRowState.Added:  // 追加された

                                cmd.CommandText = "INSERT INTO sales (stor_id, ord_num, ord_date, qty, payterms, title_id) " +
                                    $" VALUES ('{row["stor_id"]}', '{row["ord_num"]}', CAST(CURRENT_TIMESTAMP AS DATE), {row["qty"]}, '{row["payterms"]}', '{row["title_id"]}');";
                                break;

                            case DataRowState.Deleted:  // 削除された

                                cmd.CommandText = "DELETE FROM sales " +
                                    $" WHERE stor_id = '{row["stor_id", DataRowVersion.Original]}' AND ord_num = '{row["ord_num", DataRowVersion.Original]}';";
                                break;
                        }

                        cmd.ExecuteNonQuery();  // クエリを実行
                    }
                    tx.Commit();
                    textBox1.Text = "Executed.";
                }
                catch
                {
                    tx.Rollback();
                    throw;
                }
            }
            finally
            {
                cn.Close();
            }
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

## DataAdapter でデータベースから読込する・・⑭

`DataTable` の `Load()` の代わりに、`DataAdapter` の `Fill()` を使います。上記⑨のコードを書換してみます。

参考：[SqlDataReader と SqlDataAdapter の違いと使い分け - C# とデータベース - C# 入門](https://csharp.sql55.com/database/sql-data-reader-vs-sql-data-adapter.php)

`Fill()` の時点でデータベースと接続するようです。事前に `Open()` する必要ありません。`SqlConnection` オブジェクトを用意して `DataAdapter` に渡しておきます。

参考：[「[VB.NET]ADO.NETのコネクションとデータアダプタの関係について」（1） Insider.NET － ＠IT](https://atmarkit.itmedia.co.jp/bbs/phpBB/viewtopic.php?topic=14103&forum=7)

```csharp
        前略
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
//          cn.Open();                    // 不要

            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = "SELECT * FROM sales;";  // ↑ここまで同じ

            SqlDataAdapter da = new SqlDataAdapter();  // DataAdapter を用意
            da.SelectCommand = cmd;

            DataTable dt = new DataTable();  // DataTable を用意

            da.Fill(dt);  // ここで読込

            textBox0.Text = "Connected.";

            dataGridView1.Columns.Clear();  // 必要
            dataGridView1.Rows.Clear();

            dataGridView1.DataSource = dt;  // ここで表示

//          cn.Close();  // 不要
        後略
```

<details><summary>省略しないコード</summary>

```csharp
    private void button14_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
//          cn.Open();                    // 不要

            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = "SELECT * FROM sales;";

            SqlDataAdapter da = new SqlDataAdapter();  // DataAdapter を用意
            da.SelectCommand = cmd;

            DataTable dt = new DataTable();  // DataTable を用意

            da.Fill(dt);  // ここで読込

            textBox0.Text = "Connected.";

//          cn.Close();  // 不要

            dataGridView1.Columns.Clear();  // 必要
            dataGridView1.Rows.Clear();

            dataGridView1.DataSource = dt;  // ここで表示
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }

```
</details>

## DataAdapter でデータベースから読込する・・⑮

`DataAdapter` オブジェクトをメソッドの外側で宣言します。データを処理したこのオブジェクトを下記⑯など別のメソッドで参照したいからです。
上記⑩で用意した `DataTable` オブジェクト「dt1」に対して操作します。

```csharp
    private SqlDataAdapter da1 = new SqlDataAdapter();  // メソッドの外側で宣言

    private void button15_Click(object sender, EventArgs e)
    {
       中略
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = "SELECT * FROM sales;";

            da1.SelectCommand = cmd;

            dt1.Clear();  // 必要

            da1.Fill(dt1);  // ここで読込

            dataGridView1.DataSource = dt1;  // ここで表示
       後略
```

<details><summary>省略しないコード</summary>

```csharp
    private SqlDataAdapter da1 = new SqlDataAdapter();  // メソッドの外側で宣言

    private void button15_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = conStr;
//          cn.Open();

            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = "SELECT * FROM sales;";

            da1.SelectCommand = cmd;

            dt1.Clear();  // 必要

            da1.Fill(dt1);  // ここで読込される

            textBox0.Text = "Connected.";

            dataGridView1.DataSource = null;  // 必要
            dataGridView1.Columns.Clear();
            dataGridView1.Rows.Clear();

            dataGridView1.DataSource = dt1;

//          cn.Close();
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

## DataAdapter の CommandText を設定する・・⑯

上記⑮の `DataAdapter` オブジェクトに対して、`DbCommandBuilder` から派生した `SqlCommandBuilder` を使って `CommandText` を設定します。設定した `CommandText` は下記⑰の `Update()` で使用されます。

参考：[ADO.NET入門記-010 (CommandBuilderの使用(Insert,Update,Delete) (CommandBuilder, DataAdapter, Fill, Update)) - いろいろ備忘録日記](https://devlights.hatenablog.com/entry/20070829/p1)

```csharp
        SqlCommandBuilder cb = new SqlCommandBuilder();
        cb.DataAdapter = da1;
```

`DbCommandBuilder` は、`DataAdapter` に設定された `SelectCommand` の内容に従って、適切な「更新」「挿入」「削除」のクエリを自動で作成してくれます。どんなクエリが作成されたのか確認できます。

参考：[第5回　データセットを使ったレコードの追加：基礎解説 ADO.NET基礎講座　―初めての.NETデータベース・プログラミング―（3/3 ページ） - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/0312/10/news002_3.html)

```csharp
        textBox1.AppendText($"InsertCommand：{cb.GetInsertCommand().CommandText}\r\n");
        textBox1.AppendText($"UpdateCommand：{cb.GetUpdateCommand().CommandText}\r\n");
        textBox1.AppendText($"DeleteCommand：{cb.GetDeleteCommand().CommandText}\r\n");
```

<details><summary>省略しないコード</summary>

```csharp
    private void button16_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        SqlCommandBuilder cb = new SqlCommandBuilder();
        cb.DataAdapter = da1;

        textBox1.AppendText($"InsertCommand：{cb.GetInsertCommand().CommandText}\r\n");
        textBox1.AppendText($"UpdateCommand：{cb.GetUpdateCommand().CommandText}\r\n");
        textBox1.AppendText($"DeleteCommand：{cb.GetDeleteCommand().CommandText}\r\n");
    }
```
</details>

## DataAdapter でデータベースを更新する・・⑰

上記⑬はループで処理しましたが、代わりに `DataAdapter` の `Update()` を使います。
上記⑮の `DataAdapter` オブジェクト「da1」を使って、上記⑩で用意した `DataTable` オブジェクト「dt1」に対して操作します。

参考：[DataAdapterのUpdateメソッドの仕組み。猫の気ままなC#日記](http://okwakatta.net/code/ado09.html)

```csharp
        try
        {
            da1.Update(dt1);  // ここで更新

            textBox0.Text = "Updated.";
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
```

<details><summary>省略しないコード</summary>

```csharp
    private void button17_Click(object sender, EventArgs e)
    {
        textBox0.Clear();
        textBox1.Clear();

        try
        {
            da1.Update(dt1);  // ここで更新

            textBox0.Text = "Updated.";
        }
        catch (Exception ex)
        {
            textBox0.Text = "Error:" + ex.Message;
        }
    }
```
</details>

`SqlConnection` オブジェクトは、上記⑮で作成されて「da1」に紐づいて保持されています。データベースに接続するのは、`Fill()` と同様に `Update()` で実行されるので、コードに記述しません。
実行されるクエリは、上記⑯でセットされます。
諸々の処理が `DataAdapter` に組込されているので、コードは短くなりますが、何が実行されているのか分かりづらくなっていますね。↑