ここ何週間かLINQ to SQLについてブログを書いています。 LINQ to SQL とは、 .NET Framework "Orcas"リリースで出荷されるO/RM ( オブジェクトリレーショナルマッピング)の実装のことです。これにより、.NETクラスを使用してリレーショナルデータベースをモデル化することができます。そして、LINQ文を使用してデータベースの検索、またそこからデータの更新・挿入・削除も行うことができます。
以下はLINQ to SQL シリーズの最初の4つです。:
これらの前のLINQ to SQLのブログ投稿では、プログラム的にどのようにLINQ to SQLを使って簡単にデータベースのデータを検索および更新できるかにフォーカスしてお伝えしてきました。
今回の投稿では、来る.NET3.5リリースでASP.NETの一部として出荷される新しい<asp:LinqDataSource>コントロールについてカバーしていきたいと思います。このコントロールはASP.NET向けの新しいデータソースコントロールで(ASP.NET2.0で出荷されたObjectDataSourceやSQLDataSourceコントロールの様な)、宣言してASP.NET UIコントロールとLINQ to SQLデータモデルを非常に簡単にバインディングします。
バインディングするサンプルアプリケーション
このチュートリアルでバインディングする単純なデータ編集Webアプリケーションは、データベースで製品の基本的なデータ入力および操作をおこなうフロントエンドです。
アプリケーションは以下のエンドユーザ機能をサポートします。
- カテゴリ毎に製品をフィルタする
- カラムヘッダ(名前、価格、在庫数など)上をクリックすることで製品のリストがソートされる
- 複数の製品リスト (ページ当たり10製品) をスキップまたページングする
- ページ上で製品の詳細をインラインで編集および更新する
- リストから製品を削除する
WebアプリケーションはLINQ to SQL ORMを使用してビルドされたクリーンなオブジェクト指向データモデルと共に実装されます。
全てのビジネスルールやビジネス検証ロジックはデータモデル層で実装され、UI層やその他UIページ上では 行われません。これにより次のことを保証します。:1) 一貫したビジネスルールをアプリケーション内全体で使用、 2) コードは少なく繰り返さない、3) 後でビジネスルールを簡単に修正・適用することができ、アプリケーションの様々な場所にあるそれらのルールを更新する必要がない。
また、ビルドインのページング・ソートのサポートをLINQ to SQL内で活用し、製品リストのページング・ソートのような機能が中間層ではなく必ずデータベース内で実行されるようにします。(つまり、10個の製品だけがどんな場合もデータベースから取得されます。何千行も取得してそのページング・ソートをWebサーバ内で行いません。)
<asp:LinqDataSource>コントロールとは何で、そのヘルプとはどのようなもの?
<asp:LinqDataSource>コントロールはASP.NETコントロールで、ASP.NET 2.0で紹介されたDataSourceControlパターンを実装します。それは、ObjectDataSourceやSqlDataSourceコントロールと同様のものなので、その意味では宣言してASP.NETコントロールをページ上でデータソースにバンドするのに使用することができます。異なる部分は、データベース(SqlDataSourceの様に)またはジェネリッククラス(ObjectDataSourceの様に)に直接バインディングする代わりに、<asp:linqdatasource>はLINQが有効化されたデータモデルに対してバインドするように設計されています。
<asp:linqdatasource>コントロールを使用する利点の1つは、コントロールがORMに基づくLINQが提供する柔軟性を大いに活用することです。呼び出すためにデータソースに対するカスタムの検索・挿入・更新・削除メソッドを定義する必要はありません。その代わりに<asp:linqdatasource>コントロールをデータモデル内でポイントし、どのエンティティテーブルを対象にしたいのかを明らかにし、その後でASP.NET UIコントロールを<asp:linqdatasource>に対してバインドして動作させます。
例えば、LINQ to SQLデータモデル内のProductエンティティに対して動作するページ上の基本的な製品リストUIを取得するためには、単に<asp:linqdatasource>をページ上で宣言して、LINQ to SQLデータコンテキストクラスへポイントさせ、バインドさせたいLINQ to SQLデータモデル内のエンティティ(例:Products)を明らかにします。そうすれば、GridViewをそれに対してポイントさせ(DataSourceIDプロパティを設定することで)、Productコンテンツのグリッドのようなビューを取得することができます。
他に何もしなくても、ページを起動させ、データのページングやソートに対してビルドインサポートを持ったProductデータリストを得ることができます。グリッド上に編集・削除ボタンを追加して、自動的に更新のサポートも得ることができます。検索や更新シナリオを処理するために、他にメソッドを追加したり、何か引数をマップしたり、<asp:LinqDataSource> に対して何かコードを書いたりする必要はなく、ポイントしたLINQ to SQLデータモデルに対して動作し自動的にそれらの処理を行います。更新が行われた時、データベースにデータが保持される前に、LINQ to SQL ORMは自動的にLINQ to SQLデータモデルに対して追加した全てのビジネスルールや検証ロジック(partialメソッドとして)を確実に通るようにします。
重要: LINQやLINQ to SQLの美しさは明らかにUIシナリオで(またはLinqDataSourceの様な特定のUIバインディングコントロールと共に)だけ使用される様にひもづけられてはいないことです。このシリーズの前回の投稿で見たように、LINQ to SQL ORMを使用して書くコードは非常にクリーンです。必要な場合、また<asp:linqdatasource>を使用することがUIシナリオに特に合わない場合など、直接LINQ to SQLデータモデルに対して動作するカスタムUIコードを書くことができます。
以下のセクションは、LINQ to SQLや<asp:LinqDataSource>コントロールを使用して上記で定義したWebアプリケーションシナリオをビルドするチュートリアルです。
ステップ1: データモデルを定義
アプリケーションで、データベースを表示するために使用するデータモデルを定義することから始めます。
このシリーズのパート 2で、VS2008のLINQ to SQLデザイナを使用してLINQ to SQLデータモデルを作成する方法をお話しました。以下は"Northwind"サンプルデータベースをモデル化するためにLINQ to SQLデザイナを使って簡単に作成することができるデータモデルクラスのスクリーンショットです。
下方にあるこのチュートリアルのステップ5でいくつかのビジネス検証ルールを追加する時に再度データモデルに戻ります。しかし、まずはUIをビルドするために上記のデータモデルをそのまま使います。
ステップ2: 基本的なProductリストを作成
ASP.NETページ上に<asp:gridview>コントロールを乗せて作成し、スタイル化のためにいくつかのCSSを使用します。
(このシリーズのパート3で行ったように)GridViewにデータモデルをバインドするためにコードを書くこともできますが、その代わりに新しい<asp:linqdatasource>コントロールを使ってGridViewをデータモデルにバインドすることもできます。
VS 2008にはビルドインデザイナサポートがあるので、GridView(またはその他のASP.NETサーバコントロール)をLINQデータと簡単に接続することができます。前に作成したデータモデルに上記のグリッドをバインドするために、デザインビューに切り替えて、GridViewを選択し、"データソースの選択:"ドロップダウンにある"新しいデータソース..."オプションを選択します。
そうすると、ダイアログボックスに作成可能なデータソースオプションが一覧表示されます。ダイアログボックスから新しい"LINQ"オプションを選択して、最終的に作成したい<asp:linqdatasource>コントロールに名前をつけます。
<asp:linqdatasource>デザイナはアプリケーションが使用できる (参照しているクラスライブラリにあるものも含む) LINQ to SQLDataContextクラスを表示します。
前にLINQ to SQLデザイナで作成したデータモデルを選択します。<asp:linqdatasource>とバインドする時にプライマリエンティティとするテーブルをデータモデルから選択します。このシナリオではビルドした"Products"エンティティクラスを選択します。"詳細設定"ボタンを選択してデータソースに対して更新・削除を可能にします。
上記の"Finish"ボタンをクリックした時、VS2008は<asp:linqdatasource>を.aspxページ内で宣言し、(DataSourceIDプロパティを通して)それに対してポイントするように<asp:gridview>を更新します。またそれはバインドすることにしたProductエンティティのスキーマに基づいてグリッド上で自動的にカラムの宣言を行います。
GridViewの"smart task"コンテキストUIを引き出して、ページング、ソート、編集、削除の有効化を行います。
F5を押してアプリケーションを起動すると、ページングやソートがフルサポート(以下のグリッドの下にあるページングのインデックスに注意)されている製品リストページが表示されます。
各行にある"編集"や"削除"ボタンを選択してデータを更新することもできます。
ページのソースビューに切り替えた場合、以下のコンテンツを持つページのマークアップを見ることができます。<asp:linqdatasource>コントロールは前に作成したLINQ to SQLのDataContextおよびバインドしようとしているエンティティテーブルをポイントしています。GridViewは(DataSourceIDを通じて)<asp:linqdatasource>コントロールをポイントしており、グリッドに含まれているべきカラムはどれなのか、ヘッダテキストが何であるか、またカラムヘッダが選択された時に使用するソート文は何なのかを表示しています。
LINQ to SQLデータモデルに対して動作するWebUIの基礎ができたので、そのUIと動作をこれからカスタマイズしていくことができます。
ステップ3: カラムのクリーニング
上記のGridViewには定義されたカラムがたくさんあり、カラム値(SupplierIDおよびCategoryID)のうち2つは現在外部キーの数値です。これは、明らかにエンドユーザに対して表示するには理想的な方法ではありません。
不必要なカラムを削除
必要のないいくつかのカラムを削除してUIのクリーニングを開始します。ソースモード(単に<asp:boundfield>宣言を外す)またはデザイナモード(ただデザイナ上でカラムをクリックして"削除"タスクを選択する)でこれを行うことができます。例えば、以下の"QuantityPerUnit"カラムを削除してアプリケーションを再起動させると少しUIがきれいになっていると思います。
もし<asp:ObjectDataSource>コントロールを使用して、明示的に更新のパラメータを更新メソッド(TableAdaptersベースでDataSetを使用している時のデフォルト)に渡した場合、例えば、グリッド(上記のような)上で1つのカラムを削除した場合、最終的にはそのパラメータ無しで更新メソッドをサポートするためにTableAdapterを修正しなければならなくなります。
<asp:LinqDataSource> コントロールの非常にいい点はこういったタイプの変更を しなくても もいいところです。単にUIからカラムを削除(または追加)してアプリケーションを再起動するだけで、それ以外の変更は必要ありません。これにより <asp:LinqDataSource>を使用したWebUIのビルドが非常に簡単になり、アプリケーション内でより早いシナリオイテレーションが可能になります。
SupplierIDとCategoryIDカラムをクリーニング
現在、GridView内で各ProductのSupplierとCategoryに対する外部キーをinteger値で表示しています。
データモデルの観点からは正確なのですが、あまりユーザフレンドリではありません。一番したい事は、CategoryNameとSupplierNameを代わりに表示し、編集モード時にはドロップダウンリストを提供してエンドユーザが簡単にSupplierIDとCategoryID値を関連付けれるようにすることです。
GridViewにあるデフォルトの<asp:BoundField>と<asp:TemplateField>を入れ替えることで、それらのIDの代わりにSupplierの名前とCategoryの名前を表示するように変更することができます。このTemplateField内に、カラムの表示をカスタマイズしたいコンテンツはどれでも追加することができます。
以下のソースコードで、作成したLINQ to SQLデータモデルにある各ProductクラスはSupplierとCategoryプロパティを持っているという事実を活用していきたいと思います。要するに、Supplier.CompanyNameとCategory.CategoryNameのサブプロパティをグリッドの中で簡単にデータバインドできるということです。
そうすると、アプリケーションを起動したときに、人間が読みやすいCategoryとSupplierの名前の値で取得されるようになります。
グリッドの編集モード時にSupplierとCategoryのカラムに対してドロップダウンリストを取得するには、まず2つの<asp:LinqDataSource>コントロールをページに追加します。先に作成したLINQ to SQLデータモデル内でCategoriesとSuppliersに対してそれらをバインドするように構成します。
その後、前にGridViewに追加した<asp:TemplateField>カラムへ戻って、編集時の外観を(EditItemTemplateを変更することで)カスタマイズします。編集モードの時に各カラムがドロップダウンリストコントロールを持つようにカスタマイズします。ドロップダウンリストに表示される値は上記のカテゴリとサプライヤのデータソースコントロールから引っ張ってきて、ProductのSupplierIDとCategoryIDの外部キーに選択された値を2方向でデータバインドします。
これでGridViewでエンドユーザが編集をクリックすると、全ての有効なSupplierのドロップダウンリストはそれらの製品と関連づいて表示されます。
そしてそれらが保存された時、そのProductは適切に更新されます。(GridViewがドロップダウンリストの現在選択されている値を使ってSupplierIDとバインドします。)
ステップ 4: Productリストのフィルタリング
データベースの全ての製品を表示するのではなく、UIを更新してドロップダウンリストを表示するようにし、ユーザが特定のカテゴリによって製品をフィルタできるようにします。
既にLINQ to SQLデータモデルのCategoriesを参照する<asp:LinqDataSource>コントロールをページに追加しているので、後はこれに対してバインドするドロップダウンリストコントロールをトップページに作成するだけです。例:
このページを起動した時、トップページ上に全てのカテゴリのフィルタ用のドロップダウンリストが表示されるようになります。
最後のステップはGridViewを構成して、エンドユーザがドロップダウンリストから選択したカテゴリにあるProductsだけを表示するようにします。これを一番簡単に行う方法は、グリッドのスマートタスクで"データソースの構成"オプションを選択します。
このチュートリアルの最初の辺りで使用した<asp:LinqDataSource>コントロールのデザインタイムUIに戻ります。この中で"Where"ボタンを選択してデータソースコントロールにバインディングするフィルタを追加することができます。フィルタ文はいくつでも追加することができ、宣言して様々な場所から(例えば、クエリストリングから、フォームの値から、ページ上のその他のコントロールからなど)フィルタを行う値を引っ張って来ることができます。
上記ではProductsのCategoryIDでフィルタするように選択し、ページ上に作成したドロップダウンリストコントロールからこのCategoryIDを取得します。
終了すると、ページにある<asp:linqdatasource>コントロールはフィルタ節を反映するために次のように更新されます。
これで、ページを起動すると、エンドユーザはフィルタ用のドロップダウンリストで利用可能なCategoryを選択して、そのカテゴリの製品だけをページング、ソート、編集、削除することができるようになります。
LINQ to SQLデータモデルクラスを利用している場合、<asp:LinqDataSource>コントロールは自動的に適切なLINQフィルタ文を適用して、必ず必要なデータだけがデータベースから取得されるようにします。(例えば、上記のグリッドではConfection製品の2ページ目からProductデータを3行だけデータベースから取得します。)
また、もしコード上でカスタムLINQ文を書いてクエリを完全にカスタマイズしたい場合、<asp:LinqDataSource>上で選択イベントを処理することもできます。
ステップ 5: ビジネス検証ルールを追加
このLINQ to SQLシリーズのパート4 でお話しましたが、LINQ to SQLデータモデルを定義する時、データモデルクラスに追加された検証制約に基づいたデフォルトのスキーマが自動的に設定されます。つまり、もし必要なカラムにNull値を追加しようとした場合や、integerにstringを割当てようとした場合、また存在しない行に外部キー値を割当てようとした場合、LINQ to SQLデータモデルはエラーを発生させてデータベースの統一性が保たれるようにします。
基本的なスキーマの検証は最初の1歩にすぎないとはいえ、やはり実際の世界でのアプリケーションには通常は不十分です。通常の場合だと、ビジネスルールやアプリケーションレベルの検証をデータモデルクラスに追加したいまたはその必要があると思うでしょう。有難いことにLINQ to SQLだとこれらのタイプのビジネス検証ルールを簡単に追加することができます。(詳細は、このLINQ to SQLシリーズのパート 4を読んでください。)
ビジネス検証ルールシナリオの例
例えば、執行したい基本的なビジネスロジックルールを考えてみましょう。具体的には、まだ受注残を抱えている製品をユーザがキャンセルできないようにしたいとします。
もしユーザが上記の行を保存しようとしたら、この変更が保存されないようにし、適切なエラーを投げて修正方法をそのユーザに伝えます。
データモデル検証ルールの追加
このタイプのビジネス検証ルールを追加するのに不適切な場所はアプリケーションのUIレイヤです。アプリケーションのUIレイヤにそれを追加するということは、そのルールがそこの1か所だけに特定され、Productsの更新も行うアプリケーションに別のページを追加した時、自動的にそれが適用されなくなります。UIレイヤ上のビジネスルール/ロジックをいろんな場所に配置すると、アプリケーションのサイズが大きくなり、非常に大変なことになってしまいます。なぜなら、ビジネスへの変更や更新により全ての場所にあるコードに変更をかける必要が出てくるからです。
このタイプのビジネスロジック検証を特定する適切な場所は、先に定義したLINQ to SQLデータモデルクラス上です。このシリーズのパート4でお話ししたように、LINQ to SQLデザイナで生成された全てのクラスは"partial"クラスとして定義されるため、簡単にそれらに追加メソッド、イベント、プロパティを追加することができます。LINQ to SQLデータモデルクラスは自動的に検証メソッドを呼び出すので、カスタム検証ロジックをその中で実装することができます。
例えば、LINQ to SQLがProudctエンティティを保存する前に呼びだすOnValidate()というpartialメソッドを実装するpartialのProductクラスをプロジェクトに追加することができます。このOnValidate()メソッドの中で、製品がキャンセルされた場合はその製品は再注文レベルが持てないように強制する以下のようなビジネスルールを追加することができます。
一度上記のクラスをLINQ to SQLプロジェクトに追加すれば、上記のビジネスルールは誰かがデータベースを修正しようとしてデータモデルを使った場合は常に強制的に適用されることになります。これは既存のProductsを更新した場合も、新しいProductsをデータベースに追加する場合も、どちらの場合にも当てはまります。
上記のページ上で定義された<asp:LinqDataSource>はLINQ to SQLデータモデルクラスに対して動作するため、全ての更新・挿入・削除ロジックは変更が保存される前に上記の検証チェックに通らなければならなくなります。この検証を発生させるためにUI層に何かを行う必要はなく、自動的にLINQ to SQLデータモデルが使用されている部分に適用されます。
UI層に便利なエラー処理を追加
デフォルトで、ユーザが今もしGridViewのUIを使用して、無効なUnitsOnOrderとDiscontinued の値を組み合わせて入力した場合、LINQ to SQLデータモデルクラスは例外を発生させます。<asp:LinqDataSource>は順番にこのエラーを捕え、それをユーザが処理するために使用できるイベントを提供します。もし誰もそのイベントを処理しない場合は、<asp:LinqDataSource>にバインドされているGridView(もしくはそれ以外)コントロールがエラーを捕え、それを処理するユーザにイベントを提供します。もし誰もそこでエラーを処理しない場合、ページに処理するように渡します。もしなければGlobal.asax ファイルの中のglobal Application_Error() イベントハンドラへ渡して処理してもらいます。開発者はこのパス上にある場所をどこか選択し、正しいユーザエクスペリエンスを提供するすために、適切なエラー処理ロジックを挿入することができます。
上記で定義したアプリケーションに対してだと、恐らく全ての更新エラーを処理するのに最善の場所は、GridView上でRowUpdated イベントを処理しているそばです。このイベントは更新がデータソース上で行われようとした時に常に発生し、もし更新イベントが失敗した場合はその例外エラーの詳細にアクセスすることができます。以下のコードを追加してエラーが発生したかどうかをチェックし、もし発生している場合は適切なエラーメッセージをエンドユーザに表示することができます。
上記を見て頂けると、UIに検証の特別ロジックを追加していない様子を確認できると思います。その代わり、ビジネスロジックで発生させた検証エラーメッセージの文字列を取得して、適切なメッセージをエンドユーザに表示するためにそれを使用しています。(そうすれば、他の失敗エラーでもっと一般的なエラーメッセージが表示できます。)
こちらも上記を見て頂けると、エラーが発生した時にGridViewを編集モードにしたままにしている様子が確認できると思います。この方法だと、ユーザはそれらの変更を失うことなく、入力した値を修正することができ、再度"保存"をクリックしてそれらを保存することができます。その後、ページ上のどこかに<asp:literal>コントロールを"ErrorMessage"IDと一緒に追加することができ、エラーメッセージを表示させる場所を管理することができます。
これで、Productを無効な値の組み合わせで更新しようとした時に修正方法を示したエラーメッセージが表示されます。
この方法を使う利点はデータモデルのビジネスルールを追加または変更することができ、その変更を検証するためにどのUI層のコードも修正する必要はありません。検証ルールとそれに対応したエラーメッセージはデータモデルの中の1つの場所に書いて、集中データ処理し、自動的に様々な場所に適用することができます。
まとめ
<asp:LinqDataSource>コントロールだと簡単な方法でASP.NET UIコントロールをLINQ to SQLデータモデルにバインドすることができます。それにより、UIコントロールはLINQ to SQLデータモデルからデータを取得することができ、またそれに対してクリーンに更新・挿入・削除を適用することもできるようになります。
上記のアプリケーションでクリーンでオブジェクト指向のデータモデルを作るためにLINQ to SQL ORMデザイナを使いました。そしてページに3つのASP.NETのUIコントロール(GridView、ドロップダウンリスト、エラーメッセージ文字)を追加し、そこからProduct、Category、Suppllierデータをバインドするために3つの<asp:LinqDataSource>コントロールを追加しました。
そして、データモデル内で5行のビジネス検証ロジックと、11行のUIエラー処理ロジックを書きました。
結果はカスタムUIがあるシンプルなWebアプリケーションです。これは、ユーザが動的に製品データをカテゴリでフィルタでき、製品結果を効率的にソートやページングし、更新を保存する(ビジネスルールが通ったことを前提として)ために製品データをインライン編集し、製品をシステムから削除(こちらもビジネスルールが通ったことが前提として)することができます。
このシリーズの次の投稿では、もっとLINQ to SQLシナリオ(オプティミスティック同時実行制御、遅延・先行ローディング、テーブルマッピング継承、カスタムSQLおよびストアドプロシージャの使用方法など)についてカバーしたいと思います。
次の週、新しい<asp:ListView>コントロールをカバーする新しいシリーズを始めようと思っています。この新しいコントロールは.NET 3.5のASP.NETリリースで出荷される予定です。ページング、ソート、編集、挿入シナリオに対してビルドインのサポートを行っているかたわら、データシナリオ(表、Span、インラインスタイルなどの無い)に対して生成されたマークアップを総合的に管理することができます。例えば、上記のアプリケーションのデフォルトのグリッドレイアウトの外観を完全にカスタムなルックアンドフィールに入れ替えて使用することも可能です。一番いい事としては、アプリケーションの上記ページの中でそれを入れ替えることができ、データモデルや、 <asp:linqdatasource>宣言、またコードビハインドのUIエラー処理ロジックなどは全く変更する必要がありません。
Hope this helps,
Scott