データ バインディング (LINQ to SQL)
LINQ to SQL は、グリッド コントロールなどのコモン コントロールに対するバインディングをサポートしています。具体的に、LINQ to SQL では、データ グリッドにバインドし、マスター/詳細形式のバインディングを処理するための基本パターンが、表示と更新の両方に関して定義されています。
基本原則
LINQ to SQL は、LINQ クエリをデータベースで実行するために、SQL に変換します。その結果は、厳密に型指定された IEnumerable になります。これらのオブジェクトは通常の共通言語ランタイム (CLR: Common Language Runtime) オブジェクトであるため、一般的なオブジェクト データ バインディングを使用して結果を表示できます。一方、変更操作 (挿入、更新、および削除) には、追加的な手順が必要です。
操作
Windows フォーム コントロールに対する暗黙バインディングは、IListSource を実装することで実現されます。データ ソース ジェネリック Table<TEntity> (C# では Table<T>、Visual Basic では Table(Of T)) およびジェネリック DataQuery は更新されており、IListSource を実装しています。各ユーザー インターフェイス (UI) データ バインディング エンジン (Windows フォームおよび Windows Presentation Foundation) はいずれも、データ ソースが IListSource を実装しているかどうかをテストします。したがって、コントロールのデータ ソースに対するクエリの直接表示を作成すると、次の例のように LINQ to SQL のコレクション生成が暗黙的に呼び出されます。
Dim dataGrid1 As New DataGrid()
Dim dataGrid2 As New DataGrid()
Dim dataGrid3 As New DataGrid()
Dim custQuery = _
From cust In db.Customers _
Select cust
dataGrid1.DataSource = custQuery
dataGrid2.DataSource = custQuery
dataGrid2.DataMember = "Orders"
Dim bs = _
New BindingSource()
bs.DataSource = custQuery
dataGrid3.DataSource = bs
DataGrid dataGrid1 = new DataGrid();
DataGrid dataGrid2 = new DataGrid();
DataGrid dataGrid3 = new DataGrid();
var custQuery =
from cust in db.Customers
select cust;
dataGrid1.DataSource = custQuery;
dataGrid2.DataSource = custQuery;
dataGrid2.DataMember = "Orders";
BindingSource bs = new BindingSource();
bs.DataSource = custQuery;
dataGrid3.DataSource = bs;
Windows Presentation Foundation でも同じようになります。
Dim listView1 As New ListView()
Dim custQuery2 = _
From cust In db.Customers _
Select cust
Dim ItemsSource As New ListViewItem
ItemsSource = custQuery2
ListView listView1 = new ListView();
var custQuery2 =
from cust in db.Customers
select cust;
ListViewItem ItemsSource = new ListViewItem();
ItemsSource = (ListViewItem)custQuery2;
コレクション生成は、ジェネリック Table<TEntity> およびジェネリック DataQuery によって GetList で実装されます。
IListSource の実装
LINQ to SQL は、次の 2 か所で IListSource を実装しています。
データ ソースが Table<TEntity> の場合 : LINQ to SQL はテーブルを参照し、テーブルへの参照を保持する DataBindingList コレクションを設定します。
データ ソースが IQueryable<T> の場合 : 次の 2 つのシナリオがあります。
LINQ to SQL が、基になる Table<TEntity> を IQueryable<T> から見つけた場合、ソースは編集に対応し、上の場合と同じ状況になります。
LINQ to SQL が、基になる Table<TEntity> を見つけることができなかった場合、ソースは編集 (たとえば groupby) に対応していません。LINQ to SQL は、クエリを参照してジェネリック SortableBindingList を設定します。これは、指定したプロパティによる T エンティティの並べ替え機能を実装した単純な BindingList<T> です。
専用コレクション
このドキュメントでここまでに説明した多くの機能について、BindingList<T> を特化したクラスが用意されています。ジェネリック SortableBindingList クラスと、ジェネリック DataBindingList クラスです。いずれも内部クラスとして宣言されています。
ジェネリック SortableBindingList
これは BindingList<T> を継承したクラスで、並べ替え可能な BindingList<T> です。並べ替えはメモリ内で処理され、データベース自体とのやり取りは行われません。BindingList<T> は IBindingList を実装していますが、既定では並べ替えをサポートしていません。しかし、BindingList<T> は、仮想コア メソッドを持つ IBindingList を実装しています。これらのメソッドを簡単にオーバーライドできます。ジェネリック SortableBindingList は、SupportsSortingCore、SortPropertyCore、SortDirectionCore、および ApplySortCore をオーバーライドします。ApplySortCore は ApplySort によって呼び出され、指定のプロパティで T の項目のリストを並べ替えます。
そのプロパティが T にない場合、例外が発生します。
並べ替えを行うために、LINQ to SQL は、ジェネリック IComparer.Compare を継承したジェネリック SortableBindingList.PropertyComparer クラスを作成し、指定の型 T、PropertyDescriptor、および方向に応じた既定の比較演算子を実装します。このクラスは、T の Comparer を動的に作成します (T は PropertyDescriptor の PropertyType)。そして、静的なジェネリック Comparer から既定の比較演算子が取得されます。既定のインスタンスはリフレクションを使用して取得されます。
ジェネリック SortableBindingList は DataBindingList の基本クラスでもあります。ジェネリック SortableBindingList には、項目の追加と削除の追跡を中断および再開するための 2 つの仮想メソッドがあります。これら 2 つのメソッドは、並べ替えなどの基本機能にも使用できますが、実際にはジェネリック DataBindingList などの上位クラスによって実装されます。
ジェネリック DataBindingList
このクラスは、ジェネリック SortableBindingLIst を継承しています。ジェネリック DataBindingList は、最初にコレクションの読み込みに使用したジェネリック IQueryable の基になるジェネリック Table に対する参照を保持しています。ジェネリック DatabindingList では、InsertItem() と RemoveItem() がオーバーライドされて、コレクションに対する項目の追加と削除を追跡する処理が追加されています。また、追跡を中断および再開する機能の抽象メソッドが実装され、条件に応じた追跡が可能となっています。この結果、ジェネリック DataBindingList では、親クラスが持つ追跡機能のポリモーフィックな使用法がすべて活用されています。
EntitySet へのバインディング
EntitySet へのバインディングは特別なケースです。EntitySet は既に、IBindingList を実装したコレクションであるためです。LINQ to SQL では、並べ替えとキャンセル (ICancelAddNew) のサポートが追加されています。EntitySet クラスは内部リストを使用してエンティティを格納します。このリストは、ジェネリック配列 (ジェネリック ItemList クラス) を基にした低水準のコレクションです。
並べ替え機能の追加
Array には、T の Comparer を使用できる並べ替えメソッド (Array.Sort()) があります。LINQ to SQL は、このトピックで前に説明したジェネリック SortableBindingList.PropertyComparer クラスを使用して、プロパティに応じた Comparer と、並べ替えの方向を取得します。ジェネリック ItemList には、この機能を呼び出すための ApplySort メソッドが追加されています。
EntitySet 側では、並べ替えのサポートを次のように宣言する必要があります。
SupportsSorting は、true を返します。
ApplySort では、entities.ApplySort() を呼び出した後で OnListChanged() を呼び出します。
SortDirection プロパティおよび SortProperty プロパティでは、現在の並べ替えの定義を公開します。これはローカル メンバーに格納されています。
System.Windows.Forms.BindingSource を使用して EntitySet<TEntity> を System.Windows.Forms.BindingSource.DataSource にバインドした場合、BindingSource.List を更新するには EntitySet<Tentity>.GetNewBindingList を呼び出す必要があります。
System.Windows.Forms.BindingSource を使用して BindingSource.DataMember プロパティを設定し、BindingSource.DataMember で指定した、EntitySet<TEntity> を公開するプロパティを持つクラスに BindingSource.DataSource を設定した場合、BindingSource.List を更新する際に EntitySet<Tentity>.GetNewBindingList を呼び出す必要はありませんが、並べ替え機能が失われます。
キャッシュ
LINQ to SQL クエリは GetList を実装しています。Windows フォームの BindingSource クラスは、このインターフェイスがあると、1 つの接続に対して GetList() を 3 回呼び出します。LINQ to SQL は、この状況に対処するために、格納するインスタンスごとにキャッシュを実装し、生成された同じコレクションを常に返します。
キャンセル
IBindingList には AddNew メソッドが定義されています。バインドされたコレクションから新しい項目を作成するためにコントロールが使用するメソッドです。DataGridView コントロールでは、表示されている最後の行のヘッダーにアスタリスクが表示されている状況で、この機能が明確に示されます。このアスタリスクは、新しい項目を追加できることを示します。
コレクションは、この機能に加えて、ICancelAddNew も実装することができます。この機能によって、コントロールでは、キャンセルを行ったり、新しく編集された項目が検証されているかどうかを確認したりできるようになります。
ICancelAddNew は、LINQ to SQL のすべてのデータ バインド コレクション (ジェネリック SortableBindingList およびジェネリック EntitySet) に実装されます。どちらの実装でも、コードは次のように動作します。
いったん挿入した項目をコレクションから削除できます。
UI が編集をコミットしていない場合、変更を追跡しません。
編集がキャンセルされた場合 (CancelNew)、変更を追跡しません。
編集がコミットされた場合 (EndNew)、追跡を行うことができます。
新しい項目が AddNew で追加されたものでない場合、コレクションは通常どおり動作します。
トラブルシューティング
このセクションでは、LINQ to SQL のデータ バインディング アプリケーションのトラブルシューティングに役立つ可能性がある項目について説明します。
プロパティを使用する必要があります。フィールドのみの使用では不十分です。この使用は Windows フォームで必要です。
既定では、image、varbinary、および timestamp の各データベース型は、バイト配列に対応付けられます。このシナリオでは ToString() がサポートされていないため、これらのオブジェクトは表示できません。
主キーに割り当てられたクラス メンバーは setter を持ちますが、LINQ to SQL ではオブジェクト ID の変更がサポートされません。したがって、対応付けで使用されているデータベースの主キー/一意キーは更新できません。グリッドを変更すると、SubmitChanges を呼び出したときに例外が発生します。
1 つのエンティティが 2 つの別個のグリッド (たとえばマスター グリッドと詳細グリッド) にバインドされている場合、マスター グリッドで Delete を行っても、詳細グリッドには反映されません。