最近、少し前に開発されたASP.NET Web Formsのアプリケーションを仕事でメンテナンスしてます。
そこでDataSet(System.Data.DataSet)を初めて扱ったのですが、わからないことが多くて時間を費やしてしまったので、少し勉強することにしました。
DataSet=メモリ上にデータベースを構成するクラス
MSDN.aspx)ではデータのメモリ内のキャッシュと説明されていますが、これではちょっとイメージしづらいです。
元々はデータベースから取得したデータ(テーブルやレコードなど)をメモリ上でも扱えるようにするデータ構造なので、「メモリにデータベースを構成するクラス(群)」と言ったほうが捉えやすくなるかと思います。
DBなので複数のテーブルを持ち、1つのテーブルは複数のカラムと行からなる包含関係を持ちます。
- System.Data.DataSetクラス(=DB)
- System.Data.DataTableクラス(=テーブル)
- System.DataColumnクラス(=列・カラム)
- System.DataRowクラス(=行・レコード)
- System.Data.DataTableクラス(=テーブル)
基本的なデータの作成と参照
上の通り包含関係となっているので、まずはDataSetを作成する必要があります。
// DataSetの作成 var company = new DataSet();
次にDataTableとDataColumn定義してDataSetへ追加します。 ここまでがDDLに相当します。
// DataTableの作成 var employees = new DataTable("Employees"); // DataColumnをカラム名と型を指定して定義 // (ここではstring型のId、Name、UInt型のAgeの3つ) var id = new DataColumn("Id", typeof(string)); employees.Columns.Add(id); var name = new DataColumn("Name", typeof(string)); employees.Columns.Add(name); var age = new DataColumn("Age", typeof(uint)); employees.Columns.Add(age); // DataTableをDataSetに追加 company.Tables.Add(employees);
最後にレコードをDataTableのRowsプロパティへ挿入します。
// DataRowの定義 // DataRowのインスタンスはDataTable#NewRowを使用する。 var employee1 = employees.NewRow(); employee1["Id"] = "000001"; employee1["Name"] = "荒岩 一味"; employee1["Age"] = 40; // インデックスでも指定可能(Addした順番で、開始は0) var employee2 = employees.NewRow(); employee2[0] = "000002"; employee2[1] = "田中 一郎"; employee2[2] = 29; // DataTableへの挿入 employees.Rows.Add(employee1); employees.Rows.Add(employee2);
挿入したレコードの値を参照するためには、3段階でアクセスします。
- DataSetのTablesプロパティからDataTableのインスタンスを取得
- DataTableのRowsプロパティからDataRowのインスタンスを取得
- DataRowのインデクサでカラムを指定して値を取得
// カラム名の取得 var idColumnName = company.Tables[0].Columns[0].ColumnName; var nameColumnName = company.Tables[0].Columns[1].ColumnName; var ageColumnName = company.Tables[0].Columns[2].ColumnName; // レコードの値をインデックスで取得 var r = company.Tables[0].Rows[0]; Console.WriteLine($"{idColumnName}:{r[0]} " + $"{nameColumnName}:{r[1]} " + $"{ageColumnName}:{r[2]}"); // Id: 000001 Name: 荒岩 一味 Age: 40 // レコードの値をカラム名で取得 r = company.Tables["Employees"].Rows[1]; Console.WriteLine($"{idColumnName}:{r[idColumnName]} " + $"{nameColumnName}:{r[nameColumnName]} " + $"{ageColumnName}:{r[ageColumnName]}"); // Id: 000002 Name: 田中 一郎 Age: 29
VIsual StudioでDataSetクラスを定義
DataSetですが継承クラスとして定義する方法がVisual Studioで提供されています。
まずはソースコードファイルを追加するようにソリューションエクスプローラーを右クリックして追加を選び、「Visual C# アイテム」を選択し、「DataSet」アイテムを追加します。
追加すると、4つのファイルが生成されます。 重要な点として、.Designer.csファイルにはSystem.Data.DataSetを継承したCompanyDataSetクラスが新たに定義されています。
この中で、xsdファイルを選択するとデザイナが開きますので、DataTableを追加します。
DataTableにカラムを追加したり、プロパティで型やその他の制約を設定したりできます。
CompanyDataSetのメンバーを確認すると、DataSetのTablesプロパティに加えてEmployeesプロパティが追加されています。 さらにEmployeesDataTableクラスとEmployeeRowクラスがインナークラスとして定義されてます。
EmployeesDataTable(System.Data.TypedTableBaseを継承)ではDataColumnが、EmployeeRow(System.Data.DataRowを継承)ではカラムの値がそれぞれプロパティとなっており、プロパティを介してアクセスできるようになっています。
CompanyDataSetでデータを作成・参照するコードを示します。 DataSetデザイナを使わない先程の方法と比較して、異なる点が2点あります。
- DataSet、DataTable、DataColumn、DataRowを継承したクラスを使う
- プロパティを介してDataTable、DataColumn、DataRowの値へアクセスできる
- インデクサを使ったアクセスでは実行時にしかエラーとならないので、プロパティが使えることは利点
// CompanyDataSetクラスを使う var companyDataSet = new CompanyDataSet(); // CompanyDataSetのコンストラクタ内でEmployeesDataTableは生成されるので、 // 以下のように生成・追加する必要はない(追加すると同名のテーブルが2つ作られてしまう) // companyDataSet.Tables.Add(new CompanyDataSet.EmployeesDataTable()); // companyDataSet.EmployeesからCompanyDataSet.EmployeesRowのインスタンスを取得 var employee3 = companyDataSet.Employees.NewEmployeesRow(); // プロパティでアクセスできる employee3.Id = "000003"; employee3.Name = "広田 けいこ"; employee3.Age = 32; // companyDataSet.Employeesプロパティへを追加 companyDataSet.Employees.AddEmployeesRow(employee3); var idColumnName = companyDataSet.Employees.IdColumn.ColumnName; var nameColumnName = companyDataSet.Employees.NameColumn.ColumnName; var ageColumnName = companyDataSet.Employees.AgeColumn.ColumnName; var r = companyDataSet.Employees.Rows[0]; // プロパティではなくテーブル名でもアクセスできる // var r = companyDataSet.Tables[companyDataSet.Employees.TableName].Rows[0]; Console.WriteLine($"{idColumnName}:{r[idColumnName]} " + $"{nameColumnName}:{r[nameColumnName]} " + $"{ageColumnName}:{r[ageColumnName]}"); // Id:000003 Name:広田 けいこ Age:32
DataSetでLINQを使う
DataTableはコレクション構造の一種なので、LINQが使えそうな気がしてきます。
ですが、Rowsプロパティ(System.Data.DataRowCollectionクラス)はIEnumerableを実装していないので、そのままではLINQは使えません。
// WhereやSelectなどの拡張メソッドが無いためコンパイルエラーとなる companyDataSet.Employees.Rows.Where(e => e.Age >= 30).Select(e => e.Name);
その代わり、DataTableクラスのAsEnumerableメソッドを使えば、IEnumerableを実装するEnumerableRowCollectionクラスが返されるので、LINQを使えるようになります。
// AsEnumerableメソッドからIEnumerableを実装するEnumerableRowCollectionが返される var names = companyDataSet.Employees.AsEnumerable() .Where(e => e.Age >= 30).Select(e => e.Name); foreach (var name in names) { Console.WriteLine(name); }
まとめ
DataSetを使ったデータの定義や参照・更新について触れました。
データベースやASP.NET Web Formsとの連携などは、また別の機会に取り組みたいと思います。