NerdDinnerチュートリアルNerdDinnerステップ3:モデルの構築Scott Guthrie 著/Chica 訳2009/06/19 |
|
|
[これは無償の"NerdDinner"アプリケーション・チュートリアルのステップ3で、ASP.NET MVCを使用して、小さいながらも完全なWebアプリケーションを構築する手順を紹介しています。]
モデル−ビュー−コントローラのフレームワークにおいて、“モデル”という言葉は、アプリケーションのデータを表すオブジェクトのことであり、検証およびビジネス・ルールが統合されたドメイン・ロジックを表すオブジェクトのことです。モデルは多くの場合でMVCベースのアプリケーションの“中心”であり、後で説明しているように、基本的にはその動作を操作することになります。
ASP.NET MVCフレームワークはすべてのデータ・アクセス技術をサポートしており、開発者はリッチな.NETデータのオプションから選択して、それらのモデルを実装できます。これには、LINQ to Entities、LINQ to SQL、NHibernate、LLBLGen Pro、SubSonic、WilsonORM、または単なる生のADO.NET DataReaderもしくはDataSetなどが含まれます。
NerdDinnerアプリケーションでは、LINQ to SQLを使用して、データベースのデザインにかなり近い形で対応する簡単なモデルを作成し、いくつかの独自の検証ロジックとビジネス・ルールを追加します。その後、アプリケーションの残りの部分からデータ永続化の実装を独立させるのに役立ち、単体テストの実行も簡単にするリポジトリ・クラスを実装します。
LINQ to SQL
LINQ to SQLは、ORM(オブジェクト・リレーショナル・マッパー)で、.NET 3.5の一部として出荷されています。
LINQ to SQLは、私たちがコードを書いている.NETクラスに、データベースのテーブルを簡単にマッピングする方法を提供しています。NerdDinnerアプリケーションでは、これを使ってデータベースのDinnersおよびRSVPテーブルを、DinnerおよびRSVPクラスにマッピングします。DinnersおよびRSVPテーブルの列は、DinnerおよびRSVPクラスのプロパティに対応します。各DinnerおよびRSVPオブジェクトは、データベースのDinnersまたはRSVPテーブルにある個々の行を表します。
LINQ to SQLでは、データベースのデータを含んだDinnerやRSVPオブジェクトを検索または更新するのに、SQL文を手動で組み立てる必要がありません。その代わりに、DinnerやRSVPクラス、それらとデータベースとのマッピング、それらの間のリレーションシップを定義します。そうすると、LINQ to SQLは実行時に適切なSQL実行ロジックを生成するので、私たちはそれを利用できます。
VBおよびC#でサポートされているLINQ言語を使用して、データベースからDinnerおよびRSVPオブジェクトを取得する式クエリを書くことができます。これにより、書くべきコードの量が最小化され、非常にクリーンなアプリケーションを構築できます。
LINQ to SQLクラスをプロジェクトに追加
まずプロジェクトにある“Models”フォルダを右クリックして、[追加]−[新しい項目]メニュー・コマンドを選択します。
図1 |
これにより“新しい項目の追加”ダイアログがポップアップします。“データ”カテゴリで絞って、その中の“LINQ to SQLクラス”テンプレートを選択します。
図2 |
その項目に“NerdDinner”と名前を付けて“追加”ボタンをクリックします。Visual StudioはNerdDinner.dbmlファイルを\Modelsディレクトリの下に追加し、LINQ to SQLオブジェクト・リレーショナル・デザイナを開きます。
図3 |
LINQ to SQLでデータモデル・クラスを作成
LINQ to SQLでは、既存のデータベース・スキーマから簡単にデータモデル・クラスを作成できます。これを行うには、サーバ・エクスプローラからNerdDinnerデータベースを開き、そこでモデル化したいテーブルを選択します。
図4 |
そして、LINQ to SQLデザイナ上に、それらのテーブルをドラッグします。これにより、LINQ to SQLはテーブルのスキーマを使用して、自動でDinnerおよびRSVPクラスを作成します(クラスのプロパティはデータベースのテーブルの列にマッピングされています)。
図5 |
デフォルトでは、LINQ to SQLデザイナはデータベースのスキーマに基づいてクラスを作成する際に、自動的にテーブルや列の名前を“複数形化”します。例えば、上記の例にある“Dinners”テーブルは“Dinner”クラスになります。このクラスの名前付けにより、.NETの名前付け規約と一貫したモデルとすることができ、たいていの場合、私はこのデザイナによる修正が便利だと感じています(特に多くのテーブルを追加するとき)。もし、このデザイナが生成するクラスやプロパティの名前が嫌な場合は、いつでも上書きして好きな名前に変更できます。これを行うには、デザイナの中でエンティティ/プロパティ名を直接編集するか、プロパティ・グリッドで修正します。
また、デフォルトでLINQ to SQLデザイナは、テーブル間の主キー/外部キーのリレーションシップを調べ、それに応じて自動で異なるモデル・クラス間のデフォルトの“リレーションシップ関連付け”を作成します。例えば、DinnerとRSVPテーブルをLINQ to SQLデザイナにドラッグした場合、RSVPテーブルがDinnerテーブルに対して外部キーを持っているという事実に基づき、その2つの間に1対多のリレーションシップ関係があると推測します(これはデザイナで矢印によって示されます)。
図6 |
上記の関係により、LINQ to SQLは、開発者がRSVPに関連付いているDinnerにアクセスするために使用できる、強く型付けされた“Dinner”プロパティをRSVPクラスに追加します。また、Dinnerクラスに“RSVPs”コレクション・プロパティを追加するため、開発者は特定のDinnerに関連付いているRSVPオブジェクトの検索および更新ができるようになります。
以下は、新しいRSVPオブジェクトを作成し、DinnerのRSVPsコレクションへそれを追加する際のVisual StudioでのIntelliSenseの例です。LINQ to SQLが自動的にDinnerオブジェクト上に“RSVPs”コレクションを追加しているのを確認してください。
図7 |
RSVPオブジェクトをDinnerのRSVPsコレクションに追加することで、データベースのDinnerとRSVPの行の間に、外部キーによるリレーションシップの関連付けをLINQ to SQLに指示しています。
図8 |
もしデザイナによるテーブル関係のモデル化や名前付け方法が嫌な場合、それはオーバーライドできます。デザイナで関連矢印をクリックし、プロパティ・グリッドからそのプロパティにアクセスして、名前の変更、削除、修正をするだけです。NerdDinnerアプリケーションでは、構築中のデータモデル・クラスに対して、このデフォルトの関連付けルールがうまく働いており、そのままこのデフォルトの動作を使用しています。
NerdDinnerDataContextクラス
Visual Studioは、LINQ to SQLデザイナを使用して定義されたモデルとデータベース・リレーションシップを表す.NETクラスを自動的に作成します。また、ソリューションに追加された各LINQ to SQLデザイナのファイルに対して、LINQ to SQL DataContextクラスも生成します。LINQ to SQLクラス項目を“NerdDinner”と名前付けたため、その作成されるDataContextクラスは“NerdDinnerDataContext”という名前になります。このNerdDinnerDataContextクラスは、私たちがデータベースを操作するための主要な手段になります。
NerdDinnerDataContextは2つのプロパティ、“Dinners”と“RSVPs”を公開しており、これはデータベースでモデル化した2つのテーブルを表しています。データベースからDinnerまたはRSVPオブジェクトを検索したり取得したりするために、これらのプロパティに対してC#でLINQクエリを書くことができます。
以下のコードは、NerdDinnerDataContextオブジェクトのインスタンス化と、今後発生する一連のDinnersデータを取得するためのLINQクエリを実行する方法を示しています。Visual StudioはLINQクエリを書く際に、完全なIntelliSense機能を提供し、そこから返されたオブジェクトは強く型付けされていて、IntelliSenseもサポートします。
図9 |
DinnerおよびRSVPオブジェクトへのクエリが可能になるのに加えて、取得したDinnerおよびRSVPオブジェクに対して行った、すべての変更の追跡もNerdDinnerDataContextが自動的に行います。この機能を使えば、明示的にSQLの更新コードを書かなくても、簡単にデータベースへ変更を保存できます。
|
上記コードのNerdDinnerDataContextオブジェクトは、そこから取得したDinnerオブジェクトへのプロパティの変更を自動的に追跡しています。“SubmitChanges()”メソッドを呼び出したとき、適切なSQLの“UPDATE”文をデータベースに対して実行し、更新された値を保持します。
DinnerRepository クラスの作成
小さいアプリケーションでは、LINQ to SQLのDataContextクラスに対して直接コントローラから操作を行うようにし、コントローラにLINQクエリを埋め込んでも問題のないケースもあります。しかしアプリケーションが大きくなるにつれ、この方法は保守やテストが大変になります。また、複数の場所で同じLINQクエリを重複させることにもつながります。
アプリケーションの保守やテストを簡単にする1つの方法は、“リポジトリ”パターンを使用することです。リポジトリ・クラスは、データのクエリや永続化ロジックのカプセル化に貢献し、データの永続化実装の詳細をアプリケーションから取り去ります。アプリケーションのコードをクリーンにするほか、リポジト・リパターンを使用すると、今後のデータ保存の実装が簡単に変更できるようになり、実際のデータベースがなくても、アプリケーションの単体テストができるようになります。
NerdDinnerアプリケーションには、以下のシグネチャを持つDinnerRepositoryクラスを定義します。
|
注: 本章の後で、このクラスからIDinnerRepositoryインターフェイスを抽出し、コントローラ上でそれを使用して依存性の注入(dependency injection)ができるようにします。まずは単純なところから始めために、DinnerRepositoryクラスを直接操作します。
このクラスを実装するには、“Models”フォルダを右クリックして、[追加]−[新しい項目]メニュー・コマンドを選択します。“新しい項目の追加”ダイアログで、“クラス”テンプレートを選択して、“DinnerRepository.cs”という名前にします。
図10 |
そして、DinnerRespositoryクラスを以下のコードを使用して実装します。
|
DinnerRepositoryクラスを使用した検索、更新、挿入、削除
DinnerRepositoryクラスが作成できたので、それを使用してできる一般的なタスクのいくつかのコード例を見てみましょう。
■検索時の例
以下のコードはDinnerIDの値を使用して、1つのDinnerを取得します。
|
以下のコードはすべての未開催の夕食会を取得し、それをループしています。
|
■挿入と更新の例
以下のコードは、2つの新しい夕食会を追加する例です。リポジトリへの追加/修正は“Save()”メソッドが呼び出されるまでデータベースにコミットされません。LINQ to SQLは自動的にすべての変更を、データベースの1つのトランザクションにラップします。そのためリポジトリの保存時には、すべてが変更されるか何も起らないかのどちらかになります。
|
以下のコードは、既存のDinnerオブジェクトを取得し、そこにある2つのプロパティを修正します。“Save()”メソッドがリポジトリで呼び出されたとき、その変更はデータベースへコミットされます。
|
以下のコードは、ある夕食会を取得し、それにRSVPを追加します。これはLINQ to SQLが作成したDinnerオブジェクト上のRSVPsコレクションを使用して行われます(これはデータベースの中で、その2つの間に主キー/外部キーのリレーションシップがあるからです)。“Save()”メソッドがリポジトリ上で呼び出されたとき、その変更はRSVPテーブルの新しい行としてデータベースへ保存されます。
|
■削除の例
以下のコードは、既存のDinnerオブジェクトを取得し、それに削除されたというマークを付けます。“Save()”メソッドがリポジトリ上で呼び出されたときに、その削除はデータベースへコミットされます。
|
モデル・クラスに検証とビジネス・ルール・ロジックを統合
検証とビジネス・ルール・ロジックの統合は、データを扱うアプリケーションでは主要な部分になります。
■スキーマの検証
モデル・クラスがLINQ to SQLデザイナを使用して定義されたとき、データモデル・クラスにあるプロパティのデータ型は、そのデータベース・テーブルのデータ型に対応します。例えば、もしDinnersテーブルの“EventDate”列が“datetime”の場合、LINQ to SQLが作成したそのデータモデル・クラスは“DateTime”(これはビルドインの.NETデータ型です)に型付けされます。つまり、それに対してコードからint型やbool型を割り当てようとした場合にはコンパイル・エラーになり、実行時に無効なstring型をそれに明示的に変換しようとした場合には、自動的にエラーが発生します。
LINQ to SQLはまた、文字列を使用している場合に、自動的にSQLの値をエスケープ処理します。これにより、SQLインジェクション攻撃から防御します。
■検証とビジネス・ルール・ロジック
スキーマの検証は最初のステップとしては便利ですが、まれに不十分なことがあります。多くの現実的なケースでは、複数のプロパティや実行コードまでをカバーし、しばしばモデルの状態(例えば、作成/更新/削除されたものなのか、“アーカイブ化”などのドメイン特有の状況にあるのか)なども認識可能な、よりリッチな検証ロジックを指定できる能力が要求されます。モデル・クラスに対して検証ルールを定義したり、適用したりするのに使用できる、さまざまパターンやフレームワークがあり、またそれに役立つ.NETベースのフレームワークもいくつかあります。ASP.NET MVCアプリケーションでは、ほぼそれらすべてが利用できます。
NerdDinnerアプリケーションの目的では、Dinnerモデル・オブジェクト上でIsValidプロパティやGetRuleViolations()メソッドを公開するような、比較的簡単で単純なパターンを使用します。IsValidプロパティは、検証やビジネス・ルールがすべて有効かどうかについて真偽を返します。GetRuleViolations()メソッドは、すべてのルール・エラーの一覧を返します。
今回のDinnerモデルには、プロパティへ“部分クラス”を追加して、IsValidとGetRuleViolations()を実装します。部分クラスは、(LINQ to SQLデザイナが生成したDinnerクラスのように)VSデザイナで保守されるクラスへ、メソッド/プロパティ/イベントを追加するために使用でき、自分のコードがツールによりめちゃくちゃにならないようにします。\Modelsフォルダ上で右クリックして“新しい項目の追加”メニュー・コマンドを選択すると、新しい部分クラスをプロジェクトに追加できます。“新しい項目の追加”ダイアログで“クラス”テンプレートを選択して、Dinner.csという名前にします。
図11 |
“追加”ボタンをクリックしてDinner.csファイルをプロジェクトに追加し、IDEでそれを開きます。そうすると、以下のコードを使用して、基本的なルール/検証を実施するフレームワークを実装できます。
|
上記コードについて、いくつかの注意書き:
- Dinnerクラスに“partial”キーワードが付与されています。これは、それが含まれるコードはLINQ to SQLデザイナで生成/保守されるクラスと合成され、1つのクラスにコンパイルされることを意味します。
- RuleViolationクラスは、これからプロジェクトに追加するヘルパー・クラスで、これによりルール違反について、より詳細事項が提供できます。
- Dinner.GetRuleViolations()メソッドは、検証やビジネス・ルールの評価を行います(もうすぐ実装します)。そして、一連のRuleViolationオブジェクトを返し、すべてのルール・エラーについてのより詳細な情報を提供します。
- Dinner.IsValidプロパティは便利なヘルパー・プロパティで、DinnerオブジェクトがアクティブなRuleViolationsを持っているかどうかを示します。このため開発者は、Dinnerオブジェクトを使用して、いつでも自由にチェックを行えます(そして例外を発生させません)。
- Dinner.OnValidate()部分メソッドはLINQ to SQLが提供するフックであり、Dinnerオブジェクトがデータベースに保存されようとするときを知ることができます。上記のOnValidate()実装では、保存する前にDinnerにRuleViolationsがないことを保証します。もし正しくない状態であれば、例外を発生させ、LINQ to SQLにトランザクションを停止させます。
このアプローチは、検証とビジネス・ルールを統合する単純なフレームワークを提供します。では、GetRuleViolations()メソッドに以下のルールを追加しましょう。
|
C#の“yield return”機能を使用して、一連のRuleViolationsを返します。上記の最初の6つのルール・チェックは、単純にDinner上のstringプロパティがNullまたは空とならないようにします。最後のルールはもう少し面白いもので、ContactPhoneの番号のフォーマットが国のものに合致しているかを検証するために、プロジェクトに追加可能なPhoneValidator.IsValidNumber()ヘルパー・メソッドを呼び出します。
.NETの正規表現サポートを使用して、この電話番号検証サポートを実装できます。以下はプロジェクトに追加できる簡単なPhoneValidator実装で、これにより、国別のRegexパターン・チェックが追加できます。
|
■検証とビジネス・ルールの処理
上記のような検証とビジネス・ルールのコードが追加できたので、Dinnerが作成または更新されたときに、今回の検証ロジック・ルールが評価され、強制されるようになります。
開発者は以下のようなコードを書いて、いつでもDinnerオブジェクトが有効かどうかを確認し、例外を発生させることなく、その中にあるすべての違反の一覧を取得できます。
|
正しくない状態でDinnerを保存しようとすると、DinnerRepository上でSave()メソッドを呼び出すときに例外が発生します。これは、LINQ to SQLがDinnerの変更を保存する前に、自動的にDinner.OnValidate()部分クラスを呼び出すようになっており、いまDinner.OnValidate()にコードを追加して、そのDinnerにルール違反が存在する場合は例外を発生するようにしたためです。この例外を捕捉し、違反を修正するために、その一覧を取得できます。
|
検証とビジネス・ルールは、UI層内ではなくモデル層内に実装されるため、アプリケーションのすべてのシナリオにわたり適用して使えます。後でビジネス・ルールを変更または追加でき、Dinnerオブジェクトを操作するすべてのコードで、その恩恵を受けることができます。
アプリケーション全体やUIロジックに影響を与えることなく、1カ所でビジネス・ルールを変更できるという柔軟性は、うまく書かれたアプリケーションのしるしであり、MVCフレームワークが与えてくれる恩恵です。
次のステップ
データベースの検索および更新の両方に使用できるモデルができました。
では、その周辺のHTML UI体験を構築するのに使用するいくつかのコントローラとビューをプロジェクトに追加しましょう。
[注:NerdDinnerアプリケーションの完成版はhttp://nerddinner.codeplex.comからダウンロードできます。]
「NerdDinnerチュートリアル」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|