DataSetの使い方を学ぶ

最近、少し前に開発されたASP.NET Web Formsのアプリケーションを仕事でメンテナンスしてます。

そこでDataSet(System.Data.DataSet)を初めて扱ったのですが、わからないことが多くて時間を費やしてしまったので、少し勉強することにしました。

DataSet=メモリ上にデータベースを構成するクラス

MSDN.aspx)ではデータのメモリ内のキャッシュと説明されていますが、これではちょっとイメージしづらいです。

元々はデータベースから取得したデータ(テーブルやレコードなど)をメモリ上でも扱えるようにするデータ構造なので、「メモリにデータベースを構成するクラス(群)」と言ったほうが捉えやすくなるかと思います。

DBなので複数のテーブルを持ち、1つのテーブルは複数のカラムと行からなる包含関係を持ちます。

  • System.Data.DataSetクラス(=DB)
    • System.Data.DataTableクラス(=テーブル)
      • System.DataColumnクラス(=列・カラム)
      • System.DataRowクラス(=行・レコード)

基本的なデータの作成と参照

上の通り包含関係となっているので、まずは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プロパティへ挿入します。

  1. DataRowのインスタンスはDataTableのNewRowメソッドで取得
  2. 取得したDataRowインスタンスのインデクサ([])でカラムを指定して値を設定
// 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段階でアクセスします。

  1. DataSetのTablesプロパティからDataTableのインスタンスを取得
  2. DataTableのRowsプロパティからDataRowのインスタンスを取得
  3. 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」アイテムを追加します。

f:id:ohke:20161203230919p:plain

追加すると、4つのファイルが生成されます。 重要な点として、.Designer.csファイルにはSystem.Data.DataSetを継承したCompanyDataSetクラスが新たに定義されています。

f:id:ohke:20161203231121p:plain:w300

この中で、xsdファイルを選択するとデザイナが開きますので、DataTableを追加します。

f:id:ohke:20161203231136p:plain

DataTableにカラムを追加したり、プロパティで型やその他の制約を設定したりできます。

f:id:ohke:20161203231154p:plain

CompanyDataSetのメンバーを確認すると、DataSetのTablesプロパティに加えてEmployeesプロパティが追加されています。 さらにEmployeesDataTableクラスとEmployeeRowクラスがインナークラスとして定義されてます。

f:id:ohke:20161203231221p:plain:w300

EmployeesDataTable(System.Data.TypedTableBaseを継承)ではDataColumnが、EmployeeRow(System.Data.DataRowを継承)ではカラムの値がそれぞれプロパティとなっており、プロパティを介してアクセスできるようになっています。

f:id:ohke:20161203231322p:plain:w300 f:id:ohke:20161203231326p:plain:w300

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との連携などは、また別の機会に取り組みたいと思います。