ここ何週間かLINQ to SQLについてブログを書いています。 LINQ to SQL とは、 .NET Framework "Orcas"リリースで出荷されるO/RM (オブジェクトリレーショナルマッピング)の実装のことです。これにより、.NETクラスを使用してリレーショナルデータベースをモデル化することができます。そして、LINQ文を使用してデータベースの検索、またそこからデータの更新・挿入・削除も行うことができます。
以下はLINQ to SQL シリーズの最初の3つです。:
今回の投稿では、以前作成したデータモデルをどのように使うことができるのか、またそれを使って更新、挿入、削除できるのかについてカバーしたいと思います。また、どのようにしてきれいにビジネスルールとカスタムの検証ロジックをデータモデルと統合することができるかについても紹介したいと思います。
LINQ to SQL使用したNorthwindデータベースのモデル化
このシリーズのパート 2 では、VS2008にビルドインされているLINQ to SQLデザイナを使用したLINQ to SQLクラスモデルの作成方法をカバーしました。以下はNorthwindサンプルデータベースに対して作成したクラスモデルで、今回の投稿で使用します。
上記のLINQ to SQLデータデザイナを使ってデータモデルを設計した時に5つのデータモデルクラスを定義しました。:Product、Category、Customer、Order、OrderDetail 。各クラスのプロパティはデータベースの各対応テーブルのカラムにマップされます。クラスのエンティティの各インスタンスはデータベーステーブ ルの1行を表します。
データモデルを定義した時、LINQ to SQLデザイナはカスタムDataContextクラスも作成しました。それが、データベースを検索して更新や変更を適用するメインのコンダイトを提供します。上記で定義したデータモデルの例では、このクラスを"NorthwindDataContext"と名前付けられました。NorthwindDataContext クラスはデータベース内でモデル化した各テーブルを表すプロパティを持っています。(明確に言うと:Products、Categories、Customers、Orders、OrderDetails).
このブログシリーズのパート3でカバーしたように、このNorthwindDataContext クラスを使ってデータベースからデータを検索・取得するLINQシンタックス文を簡単に使うことができます。LINQ to SQLはその後自動的にそれらのLINQクエリ文をランタイムに実行する適切なSQLコードに翻訳します。
例えば、以下のようなLINQ文を書けば、Productの名前で検索して1つのProductオブジェクトを取得できます。
以下のようなLINQクエリ文を書けば、データベースからまだ注文のない100ドル以上の全ての製品を取得することができます。
上記でどのように私が各製品に対して"OrderDetails"のアソシエーション(関連性)をクエリの一部に使用して、まだ注文されていない製品だけを取得しているかを確認してください。
変更トラッキングと DataContext.SubmitChanges()
クエリを実行して上記の製品インスタンスの様なオブジェクトを取得する時、LINQ to SQLはデフォルトでそれらのオブジェクトに後で適用する変更や更新を全てトラッキングします。LINQ to SQL DataContextを使って、必要な数だけクエリや変更を作成することができ、それらの変更は全て一緒にトラッキングされます。
注意:LINQ to SQLの変更トラッキングは消費呼び出し側で起こります。- データベース内ではないです。つまり、それを使用している時にどのデータベースのリソースも消費していませんし、それを有効にするためにデータベースで何かを変更したりインストールする必要もありません。
LINQ to SQLから取得したオブジェクトに変更を加えた後、データベースにその変更を適用するためにDataContext上で"SubmitChanges()"を呼び出しすることも可能です。これにより、LINQ to SQLは動的に計算し、データベースを更新するために適切なSQLコードを実行します。
例えば、データベースの"Chai"製品の単価および在庫数を更新するには、以下のようなコードを書くことができます。
上記のnorthwind.SubmitChanges()を呼び出した時、LINQ to SQLは動的にSQLの"UPDATE"文を構成・実行し、それにより上記で修正した2つの製品プロパティの値が更新されます。
その後、以下の様なコードで、人気のない高価な製品をループし、"ReorderLevel"プロパティを0に設定します。
上記のnorthwind.SubmitChanges() を呼び出した時、LINQ to SQLは適切なUPDATE文を計算・実行して、ReorderLevel プロパティが変更された製品を修正します。
Productのプロパティの値が上記のプロパティに設定された値に変更されていない場合は、そのオブジェクトが変更されたとは考えられない為、LINQ to SQLはデータベースにその製品への更新を実行しませんのでご注意ください。例えば、既に"Chai"製品の単価が$2で在庫数が4つだった場合は、SubmitChanges()はデータベースの更新文を一切実行しません。また、2つ目の例にあるまだReorderLevelが0でない製品はSubmitChanges()メソッドが呼ばれた時に更新されます。
例を挿入・削除
データベースに既存の行を更新するのに加え、当然LINQ to SQLでデータの挿入・削除も行うことができます。これはDataContextテーブルのコレクションからオブジェクトを追加・削除し、SubmitChanges()メソッドを呼び出すことで行うことができます。SubmitChanges()が呼び出された時、LINQ to SQLはこれらの追加・削除のトラッキングを行い、自動的に適切なSQLのINSERTまたDELETE文を実行します。
新規Productを挿入
"Product"クラスインスタンスを新しく作成してプロパティを設定し、DataContextの"Products"コレクションにそれを追加することで、新しい製品をデータベースに追加することができます。
上記の"SubmitChanges()"を呼び出した時、新しい行が製品テーブルに作成されます。
Productsの削除
ProductオブジェクトをDataContextのProductsコレクションに追加することによって、新しいProductをデータベースに追加したいことが表現できるのと同じように、DataContextのProductsコレクションからそれを削除することによって、データベースから製品を削除したいことを表現することができます。
上記を確認して頂けると、どのようにLINQクエリを使用して誰も注文しない中止製品のシーケンスを取得して、それをDataContextの"Products"コレクション上でRemoveAll()メソッドに渡しているかがお分かりいただけると思います。上記の"SubmitChanges()"を呼び出した時、これらのProduct行の全てが製品テーブルから削除されます。
リレーションシップを渡る更新
何がLINQ to SQLのようなO/Rマッパーを究極に柔軟にしているのかというと、それらのマッパーが簡単にデータモデルをまたいだクロステーブルリレーションシップをモデル化できるようにしていることです。例えば、各ProductがCategory内に含まれるように、各Orderが行の項目に対してOrderDetailsを含めているようにモデル化し、各OrderDetailの行の項目とProductを関連付け、各Customerに関連付けられているOrdersの一式が含まれるようにすることができます。このシリーズのパート2でこれらのリレーションシップを構成・モデル化する方法をカバーしました。
LINQ to SQLによりデータの検索および更新の両方でこれらのリレーションシップが活用できるようになります。例えば、以下の様なコードを書くことで、新しいProductを作成し、それをデータベースの既存の"Beverages"カテゴリに関連付けることができます。:
上記でどのようにProductオブジェクトをCategoryのProductsコレクションに追加しているかを確認してください。これは、2つのオブジェクト間にリレーションシップがあることを意味し、"SubmitChanges()"を呼び出した時にその2つの間の外部キー・主キーの関係をLINQ to SQLが自動的に保持するようにします。
またLINQ to SQLがどの様にクロステーブルリレーションシップを管理する手助けをし、コードのクリーンアップの手助けを行うかについての別の例ですが、以下で既存の顧客に対して新しいOrderを作成しているところを見てみましょう。ある注文の出荷日と運賃を設定した後、顧客が注文した製品をポイントする2つの注文の行項目(ラインアイテム)オブジェクトを作成します。その後、顧客と注文を関連付けし、全ての変更をデータベースに反映します。
見てお分かりのように、これを全て実行するプログラミングモデルは究極にクリーンでオブジェクト指向です。
トランザクション
トランザクションはデータベース(またはその他のリソースマネージャ)が提供するサービスの1つで、一連の個別のアクションは個々の要素毎に起こります。-つまりそれらは全て成功なのか違うのかのどちらかで、もし違うのなら全てが発生する前にそれらすべてを自動的に元に戻します。
DataContext上でSubmitChanges() を呼び出した時、更新は常にトランザクションにラップされます。要するに、もし複数の変更を実行した場合にデータベースが不一致な状態にはなりません。-DataContext上で変更されたもの全てが保存されている場合もされていない場合も。
トランザクションがまだスコープ内にない場合、SubmitChanges()を呼び出した時、LINQ to SQLのDataContextオブジェクトは更新を保護するために自動的にデータベースのトランザクションを始めます。それを行う代わりに、LINQ to SQLで自分自身のTransactionScopeオブジェクト(.NET2.0で紹介された機能)を明示的に定義・使用することも可能です。これにより、LINQ to SQLコードと既にある既存データへのアクセスコードを統合することが簡単になります。またトランザクションにデータベース以外のリソースを追加することもできます。- 例えば、MSMQメッセージの送信、ファイルシステム(新しいトランザクションファイルシステムのサポートを使用して)の更新などを行うことができます。-そしてLINQ to SQLでデータベースを更新する時に使用するトランザクションの中でそれらの項目全てを実行できるようにします。
検証とビジネスロジック
データを取り扱う時に開発者が考える必要のある重要事項の1つに、検証とビジネスロジックの組み込み方法があります。有難い事に、LINQ to SQLは開発者がきれいにこれをそれらのデータモデルと統合するための様々な方法をサポートしています。
LINQ to SQLにより、この検証ロジックを一度追加すると、作成したデータモデルがどこでどのように使用されているのかに関わらずそれは遵守されます。これは複数の場所でのロジックの繰り返しを避け、そしてデータモデルをより保守可能でクリーンな状態に導きます。
スキーマの検証サポート
VS2008でLINQ to SQLデザイナを使用してデータモデルクラスを定義した場合、デフォルトでデータベースのテーブルのスキーマから推測したいくつかの検証ルールが注釈されます。
データモデルクラスのプロパティのデータ型はデータベースのスキーマのデータ型と一致します。つまり、decimal値にboolean値を割当てようとした場合、また数値型を間違った変換を暗黙的に行おうとした場合に、コンパイルエラーが起こります。
データベースのあるカラムがnullableだった場合、LINQ to SQLデザイナが作成したデータモデルクラスのそれと対応するプロパティがnullable型になります。もしnull値でインスタンスを保持しようとすると、nullable型ではないカラムは自動的に例外を発生させます。LINQ to SQLは同様にそのデータベースにある同一/一意のカラム値が正しく遵守されている状態にします。
当然必要なら、LINQ to SQLデザイナを使って、それらの検証設定のデフォルトスキーマをオーバーライドすることができます。しかし、デフォルトでは自動的にそれらを取得し、それらを可能にするために他に何かをする必要はありません。LINQ to SQLはまた自動的にSQL値をエスケープ処理するので、それを使用している時にSQLインジェクションアタックを心配する必要はありません。
カスタムプロパティ検証サポート
データ型検証駆動のスキーマは最初のステップとしては有効的ですが、実際のシナリオにおいては十分でないことが多いです。
例えば、Northwindデータベースで、データベースでnvarcharとして定義されている"Customer"クラス上に"Phone"プロパティがあるというシナリオを考えてみましょう。LINQ to SQLを使用している開発者は以下のようなコードを書いて、有効な電話番号を使用してそれを更新することができます。
しかしながら、アプリケーションにおいて直面する問題は、以下のコードもまた純粋なSQLスキーマの観点から正当なものであることです。(なぜなら、それは有効な電話番号ではありませんが、1つのstringには違いないからです。)
データベースに偽の電話番号が追加されないようにするために、カスタムプロパティ検証ルールをCustomerデータモデルクラスに追加することができます。この機能を使って電話番号を検証するルールを追加するのは非常に簡単です。必要なことは、新しいpartialクラスをプロジェクトに追加して以下のメソッドを定義することだけです。
上記のコードはLINQ to SQLの2つの特徴を活用しています。
1) 全てのLINQ to SQLデザイナで作成されたクラスは"partical"クラスとして宣言されます。-つまり、開発者は簡単に追加メソッド、プロパティ、イベントを簡単にそれらに追加することができます。これにより、LINQ to SQLデザイナで作成されたデータモデルクラスとDataContextクラスをあなたが定義した検証ルールと追加のカスタムヘルパーメソッドで拡張させることが非常に簡単になります。その他の構成またはコードをつなげる必要はありません。
2) LINQ to SQLは多くのカスタム拡張ポイントをそのデータモデルとDataContextクラスに公開していて、それらは検証ロジックが発生する前後に追加して使用することができます。多くのそれらの拡張ポイントはVS2008のベータ2でVBとC#に導入された"partialメソッド"と呼ばれる新しい言語機能を活用します。C#チームのWes Dyerがpartialメソッドがどのように動作するかを上手く ここのブログ投稿で説明しています。
上記の検証例では、プラグラム的に誰かがCustomerオブジェクト上で"Phone"プロパティを設定した時に実行されるOnPhoneChangingというpartialメソッドを使用しています。このメソッドを使ってどのような形でも入力を検証することができます。(今回の場合正規表現を使っています。)もし全てが上手く通った場合、そのメソッドからreturnするだけで、LINQ to SQLはその値が有効であると見なします。もし何かその値に問題があれば、検証メソッド内で例外を発生させることができます。それによりそのタスクの発生を防止します。
カスタムエンティティオブジェクト検証サポート
上記のシナリオで使われたプロパティレベルの検証はデータモデルクラス上で個々のプロパティを検証する時に非常に有効的です。たまにですが、複数の値を1つのオブジェクト上でお互いに検証させたいことがあると思います。
"OrderDate"と"RequiredDate"プロパティの両方を設定したOrderオブジェクトでのシナリオを例として考えてみましょう。
上記のコードは純粋なSQLデータベースの観点からは正当なものですが、新しい注文で要求されている配達日が昨日になっており、全く現実的ではありません。
いいニュースですが、ベータ2のLINQ to SQLでは簡単にカスタムエンティティレベル評価ルールが追加できるため、このような間違いが起こらないように防御することができます。"Order"エンティティにpartialクラスを追加してOnValidate() partialメソッドを実装することができます。これをデータベースにエンティティの値が保持される前に発生させます。この検証メソッドの中で全てのデータモデルクラスのプロパティにアクセスし評価することができます。
この検証メソッドの中で、全エンティティのプロパティの値をチェックし(その関連オブジェクトの読取専用アクセスも取得し)、もし値が間違っていたら必要に応じて例外を発生させることができます。OnValidate()メソッドから例外が発生した場合は、データベースに保持されている変更は全て破棄され、トランザクションにあるその他の変更全てがロールバックされます。
カスタムエンティティ挿入・更新・削除メソッドの検証
挿入、更新、削除のシナリオの場合にだけ検証ロジックを追加したい場合があると思います。ベータ2のLINQ to SQLは、DataContextクラスを拡張するpartialクラスを追加し、データモデルエンティティに対する挿入、更新、削除ロジックをカスタマイズするpartialメソッドを実装できるようにすることでこれを可能にします。これらのメソッドは、DataContext上でSubmitChanges()を発生させた時に自動的に呼び出されます。
これらのメソッドの中に適切な検証ロジックを追加することができます。そして、もしそれが通った場合はLINQ to SQLにデータベースに(DataContextの"ExecuteDynamicXYZ"メソッドを呼び出すことで)関連する変更を保持し続けるように指示します。
上記のメソッドを追加する利点は、シナリオロジックがデータオブジェクトを作成、更新、削除のどれを発生させるもののであるのかに関わらず、適当なものが自動的に呼び出されることです。例えば、新しいOrderを作成し、それを既存のCustomerに関連付けた場合の簡単なシナリオを考えてみましょう。
上記でnorthwind.SubmitChanges()を呼び出した時、LINQ to SQLは、新しいOrderオブジェクトを保持する必要があるのかを決定し、自動的に"InsertOrder"のpartialメソッドを発生させます。
上級:トランザクションに対して全ての変更リストを確認
追加した検証ロジックは、純粋に個々の挿入・更新・削除の実行を見ているだけでは完了しない場合があり、その場合にトランザクションに対して発生する動作の全変更リストが見れる状態を望まれると思います。
.NET 3.5のベータ2からは、LINQ to SQLにより、publicのDataContext.GetChangeList()メソッドを呼び出すことでこの変更リストにアクセスできるようになりました。これは既に行われた各追加、削除、修正のコレクションを公開しているChangeListオブジェクトを返します。
上級のシナリオで採用することが可能な1つのアプローチとして、DataContextクラスをサブクラスにして、そのSubmitChange()メソッドをオーバーライドします。そうすると、その更新に対してChangeList()を取得し、それが実行される前に必要なカスタム検証を実行することができます。
上記のシナリオは少し上級なものですが、必要な時にいつでもドロップダウンしてそれが活用できるということを知っていて損ということはありません。
オプティミスティック同時実行制御で同時変更を処理
マルチユーザデータベースシステムで開発者が考える必要があることの1つは、データベースで同じデータの同時更新をどのように行うかです。例えば、2人のユーザがアプリケーション内で製品オブジェクトを検索していると仮定しましょう。そのユーザの内の1人がReorderLevelを0に変更し、もう1人が1へ変更したとします。開発者は、両方のユーザがその後データベースにその製品を保存しようとした場合にその変更の衝突をどのように処理するか決定する必要があります。
1つのアプローチは単に"最後に書いたもの勝ち"にすることです。つまり、最初のユーザが送信した値はそのユーザが知ることなく失われます。これは通常貧しい(そして間違った)アプリケーションエクスペリエンスとして考えられています。
LINQ to SQLがサポートしているもう1つのアプローチは、オプティミスティック同時実行制御モデルを使用します。それは、LINQ to SQLは、新しい値がデータベースに保持される前に、データベースのその元の値が他の人によって更新されたかどうかを自動的に検知します。LINQ to SQLは開発者に変更された値の衝突リストを提供するので、その差異を調和させるか、またはUIを提供してエンドユーザに意思表示させることができます。
今後の投稿でLINQ to SQLでオプティミスティック同時実行制御を使う方法をカバーしたいと思います。
挿入・更新・削除シナリオにストアドプロシージャまたはカスタムSQLロジックを使用
カスタムSQLでストアドプロシージャを書くことに慣れている開発者(特にDBA)がよく聞く質問の1つですが、最初にLINQ to SQLを見た時に、"でも裏方で実行されているSQLをどのようにすれば完全に制御することができるのですか?"と聞かれます。
いいニュースですが、LINQ to SQLは非常に柔軟なモデルを持っているので、開発者はLINQ to SQLで自動的に実行されている動的SQLをオーバーライドし、自分自身(またはDBA)が定義したカスタムの挿入、更新、削除のストアドプロシージャを呼ぶことができます。
非常にいいところは、データモデルを定義することから始めて、自動的にLINQ to SQLに挿入、更新、削除SQLロジックを処理させることができます。そして、後で更新に対してカスタムストアドプロシージャまたはSQLを使うようにデータモデルをカスタマイズすることができます。これはデータモデルを使用しているアプリケーションロジックを何も変更せず、またそれをサポートしている検証やビジネスロジックを何も変更せずに行うことができます。(全てが同じままです。)これによりアプリケーションのビルド方法に大きな柔軟性が与えられます。
今後のブログ投稿で、ストアドプロシージャまたはカスタムSQLを使用してデータモデルをカスタマイズする方法をカバーしていきたいと思います。
まとめ
上記の投稿で、どのようにして簡単にLINQ to SQL使用してデータベースを更新し、クリーンにデータモデルと検証とビジネスロジックを統合するのかについて上手くまとめることができていれれば幸いです。LINQ to SQLにより、データを扱う際の生産性を劇的に向上し、クリーンなオブジェクト指向のデータアクセスコードを書くことができるようになっていると思われるのではないかと思います。
このシリーズの次回のブログ投稿で、.NET3.5にある新しい<asp:linqdatasource>コントロールをカバーし、LINQ to SQLデータモデルを活用したASP.NETのデータUIをどのように簡単にビルドすることができるかについてお話したいと思います。またオプティミスティック同時実行制御、遅延・先行ローディング、テーブルマッピング継承、カスタムSQL・ストアドプロシージャの使用方法などの特定のLINQ to SQLプログラミング概念をもう少しカバーしたいと思います。
Hope this helps,
Scott