NerdDinner。 ステップ3:モデルを構築する

これは無料のNerdDinnerチュートリアルの3番目のステップで、 ASP を使用して小規模ながら完全なWebアプリケーションを構築する方法を示します NET MVC。

モデルビューコントローラータイプのフレームワークでは、「モデル」という用語は、アプリケーションデータを表すオブジェクト、および検証とビジネスルールを統合する対応するドメインロジックを指します。 多くの場合、モデルはMVCアプリケーションの「中心」であり、後で見るように、その基本的な動作を制御します。

ASP.NET MVCフレームワークは任意のデータアクセステクノロジーの使用をサポートしているため、開発者は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は、.NET 3.5の一部として出荷されるORM(Object Relational Projection)です。

LINQ to SQLは、データベーステーブルを.NETクラスに関連付ける簡単な方法を提供します。 NerdDinnerアプリケーションでは、夕食会とRSVPテーブル、および夕食会とRSVPクラスを使用してリンクします。 DinnersおよびRSVPテーブルの列は、DinnerおよびRSVPクラスのプロパティに対応します。 ディナーとRSVPオブジェクトはそれぞれ、データベースのディナーテーブルまたはRSVPテーブルの個別の行を表します。

LINQ to SQLは、データベース内のデータでDinnerおよびRSVPオブジェクトを受信または更新するSQLクエリの手動構築を回避します。 代わりに、Dinner RSVPクラスがデータベースと通信する方法を宣言し、クラス間の関係を定義します。 LINQ to SQLは、オブジェクトの操作中に使用する適切なSQLロジックを作成します。

VBおよびC#でサポートされているLINQ言語を使用して、データベースからDinnerおよびRSVPオブジェクトを返すクエリを作成できます。 これにより、記述する必要があるコードのサイズが最小化され、非常にクリーンなアプリケーションを構築できます。

プロジェクトへのLINQ to SQLクラスの追加


プロジェクトの「Models」フォルダーを右クリックして、「 追加」> 「新規 アイテム 」を選択します。



[新しい項目の追加]ウィンドウで、[データ]カテゴリでフィルタリングし、[LINQ to SQLクラス]テンプレートを選択します。



要素に「NerdDinner」という名前を付けて追加します。 Visual Studioは、NerdDinner.dbmlファイルを\ Modelsディレクトリに追加し、LINQ to SQLオブジェクトリンクコンストラクターを開きます。



LINQ to SQLを使用したデータモデルクラスの作成


LINQ to SQLを使用すると、既存のデータベーススキーマからデータモデルクラスをすばやく作成できます。 これを行うには、サーバーエクスプローラーでNerdDinnerデータベースを開き、モデルを作成するテーブルを選択します。



次に、テーブルをLINQ to SQLコンストラクターにドラッグします。 これを行うと、LINQ to SQLは、データベーステーブルの列に関連付けられているクラスプロパティを含むテーブルスキーマを使用して、DinnerクラスとRSVPクラスを自動的に作成します。



既定では、LINQ to SQLコンストラクターは、クラスの作成中にテーブル名と列名の複数形を自動的に処理します。 たとえば、テーブル「夕食」はクラス「夕食」になります。 このクラスの命名は、モデルが.NET命名規則との互換性を維持するのに役立ち、多数のテーブルを追加するときに便利です。 コンストラクターが生成したクラスまたはプロパティの名前が気に入らない場合は、コンストラクターまたはプロパティで編集することにより、いつでも適切な名前に変更できます。

また、既定では、LINQ to SQLコンストラクターはテーブルのプライマリキーと外部キーを見つけ、それらに基づいてモデルの異なるクラス間の関係を自動的に作成します。 たとえば、DinnersテーブルとRSVPテーブルをLINQ to SQLコンストラクターにドラッグすると、RSVPテーブルにはDinnersテーブルへの外部キーが含まれるため、1対多の関係が作成されました(コンストラクターの矢印で示されます)。



この関係により、LINQ to SQLは厳密に型指定された「Dinner」プロパティをRSVPクラスに追加でき、開発者はこれを使用してRSVPにバインドされたディナーにアクセスできます。 また、DinnerクラスにRSVPプロパティのコレクションを含めることができます。これにより、開発者は、DinnerにアタッチされたRSVPオブジェクトを取得または変更できます。

以下は、新しいRSVPオブジェクトを作成し、DinnerクラスオブジェクトのRSVPコレクションに追加するときのVisualStudioのインテリセンスの例です。 LINQ to SQLがディナークラスのオブジェクトのRSVPオブジェクトのコレクションを自動的に追加したことに注意してください。



RSVPオブジェクトをDinnerオブジェクトのRSVPコレクションに追加し、LINQ to SQLに外部キーを介してデータベース内のDinner文字列とRSVPを接続するように指示します。



繰り返しになりますが、デザイナーがテーブル間のリレーションシップと呼んでいるものが気に入らない場合は、それも変更できます。 デザイナーの接続矢印をクリックして、名前を変更、削除、変更します。 このアプリケーションでは、デフォルトの通信ルールはデータモデルクラスに最適です。

クラスNerdDinnerDataContext


Visual Studioは、LINQ to SQLコンストラクターを使用して、モデルと関係を表す.NETクラスを自動的に作成します。 また、コンストラクターによって作成された各LINQ to SQLファイルに対して、DataContextクラスが作成されます。 LINQ to SQLクラスに「NerdDinner」という名前を付けたため、DataContextクラスには「NerdDinnerDataContext」という名前が付けられ、データベースと直接対話します。

NerdDinnerDataContextクラスには、データベースで作成した2つのテーブルを表す「Dinners」と「RVSPs」という2つのプロパティが含まれています。 C#を使用してこれらのプロパティを使用してLINQクエリを記述し、データベースからDinnerおよびRSVPオブジェクトを取得できます。

次のコードは、今後の夕食会のキューを取得するためのLINQ要求を使用したNerdDinnerDataContextオブジェクトの操作を示しています。 Visual Studioは、LINQクエリの作成中に完全なインテリセンスサポートを提供します。 要求が返したオブジェクトは強く型付けされています:



さらに、NerdDinnerDataContextは、DinnerおよびRSVPオブジェクトの変更を自動的に監視し、この機能を使用して、SQL UPDATEコードを記述せずにデータベースへの変更を簡単に保存できます。

たとえば、LINQクエリを使用して、Dinnerクラスの1つのオブジェクトをデータベースから取得し、2つのDinnerプロパティを変更して、変更をデータベースに保存する方法を見てみましょう。
NerdDinnerDataContext db = new NerdDinnerDataContext();

// Dinner, DinnerID 1
Dinner dinner = db.Dinners.Single(d => d.DinnerID == 1);

// Dinner
dinner.Title = "Changed Title" ;
dinner.Description = "This dinner will be fun" ;

//
db.SubmitChanges();

NerdDinnerDataContextオブジェクトは、結果のDinnerオブジェクトのプロパティに加えた変更を自動的に監視します。 「SubmitChanges()」メソッドを呼び出すと、対応するSQL「UPDATE」クエリが実行され、変更がデータベースに保存されます。

DinnerRepositoryクラスを作成する


小規模なアプリケーションの場合、コントローラーがLINQ to SQL DataContextクラスと直接連携し、LINQクエリがコントローラー内にあると便利な場合があります。 しかし、アプリケーションの成長に伴い、このキャンペーンはテストにとって面倒で不便になります。 また、さまざまな場所でLINQコードの重複が発生します。

しかし、プロジェクトのサポートとテストを容易にするアプローチがあります-これがリポジトリ設計パターンです。 リポジトリクラスは、データ要求とロジックをカプセル化し、アプリケーションからのデータ実装の詳細を抽象化します。 さらに、コードを簡潔にするために、リポジトリパターンを使用すると、将来データウェアハウスの実装を変更でき、実際のデータベースを必要としないため、アプリケーションの単体テストも容易になります。

NerdDinnerプロジェクトでは、DinnerRepositaryクラスを次の署名で宣言します。
public class DinnerRepository {
//
public IQueryable<Dinner> FindAllDinners();
public IQueryable<Dinner> FindUpcomingDinners();
public Dinner GetDinner( int id);

// /
public void Add(Dinner dinner);
public void Delete(Dinner dinner);

//
public void Save();
}

後で、このクラスからIDinnerRepositoryインターフェースを抽出して、コントローラーでの 依存性注入を許可します。 ただし、最初に、 DinnerRepository クラスを直接操作します

このクラスを実装するには、「モデル」フォルダーを右クリックし、「 追加> 「新規 アイテム 」を選択します。 開いたウィンドウで、「クラス」テンプレートを選択し、ファイルに「DinnerRepository.cs」という名前を付けます。



次に、DinnerRepositoryクラスに次のコードを入力します。
public class DinnerRepository {
private NerdDinnerDataContext db = new NerdDinnerDataContext();

//
//
public IQueryable<Dinner> FindAllDinners() {
return db.Dinners;
}

public IQueryable<Dinner> FindUpcomingDinners() {
return from dinner in db.Dinners
where dinner.EventDate > DateTime .Now
orderby dinner.EventDate
select dinner;
}

public Dinner GetDinner( int id) {
return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
}

//
// Insert/Delete
public void Add(Dinner dinner) {
db.Dinners.InsertOnSubmit(dinner);
}

public void Delete(Dinner dinner) {
db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs);
db.Dinners.DeleteOnSubmit(dinner);
}

//
//
public void Save() {
db.SubmitChanges();
}
}

DinnerRepositoryクラスを使用したデータの取得、変更、追加、削除


DinnerRepositoryクラスを作成したので、一般的なタスクの実装を示すいくつかの例を見てみましょう。

リクエスト例


次のコードは、DinnerID値を使用して1つの夕食会を返します。
DinnerRepository dinnerRepository = new DinnerRepository();

// DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);


以下のコードは、今後のディナーをすべて返し、各サイクルを通過します。
DinnerRepository dinnerRepository = new DinnerRepository();

//
var upcomingDinners = dinnerRepository.FindUpcomingDinners();

//
foreach (Dinner dinner in upcomingDinners) {
Response.Write( "Title" + dinner.Title);
}


* This source code was highlighted with Source Code Highlighter .

追加と削除の例


次のコードは、2つの新しいディナーの追加を示しています。 リポジトリへの追加や変更は、「Save()」メソッドを呼び出すまで適用されません。 LINQ to SQLはデータベーストランザクションのすべての変更を自動的にラップするため、すべての変更が実行されるか、単一の変更ではありません。

DinnerRepository dinnerRepository = new DinnerRepository();

//
Dinner newDinner1 = new Dinner();
newDinner1.Title = "Dinner with Scott" ;
newDinner1.HostedBy = "ScotGu" ;
newDinner1.ContactPhone = "425-703-8072" ;

//
Dinner newDinner2 = new Dinner();
newDinner2.Title = "Dinner with Bill" ;
newDinner2.HostedBy = "BillG" ;
newDinner2.ContactPhone = "425-555-5151" ;

//
dinnerRepository.Add(newDinner1);
dinnerRepository.Add(newDinner2);

//
dinnerRepository.Save();

以下のコードは、既存のDinnerオブジェクトを返し、その2つのプロパティを変更します。 リポジトリメソッド「Save()」を呼び出した後、プロパティはデータベースに保存されます。
DinnerRepository dinnerRepository = new DinnerRepository();

// DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Dinner
dinner.Title = "Update Title" ;
dinner.HostedBy = "New Owner" ;

//
dinnerRepository.Save();

次のコードはdinnerを返し、それにRSVPを追加します。 これは、LINQ to SQLによって(外部キーに基づいて)作成されたDinnerオブジェクトのRSVPコレクションを通じて行われます。 これらの変更は、「Save()」メソッドを呼び出すと、RSVPテーブルの新しい行としてデータベースに送信されます。
DinnerRepository dinnerRepository = new DinnerRepository();

// DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// RSVP
RSVP myRSVP = new RSVP();
myRSVP.AttendeeName = "ScottGu" ;

// RSVP RSVP Dinner
dinner.RSVPs.Add(myRSVP);

//
dinnerRepository.Save();

例を削除


以下のコードは、既存のDinnerオブジェクトを返し、削除済みとしてマークします。 「Save()」メソッドを呼び出すと、データベースから削除されます。
DinnerRepository dinnerRepository = new DinnerRepository();

// DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

//
dinnerRepository.Delete(dinner);

//
dinnerRepository.Save();

チェックとビジネスロジックルールをモデルクラスに統合する


データを処理するアプリケーションの重要な部分は、チェックとビジネスロジックルールの統合です。

回路チェック


LINQ to SQLコンストラクターを使用してモデルクラスを宣言すると、モデルクラスのプロパティのデータ型はデータベーステーブルのデータ型に対応します。 たとえば、Dinnersテーブルの「EventData」列が「datetime」型の場合、LINQ to SQLによって作成されるデータモデルクラスは「.NETTime」型になります。これは.NETのネイティブデータ型です。 その結果、このプロパティに整数またはブール値を割り当てようとすると、コンパイル段階でエラーが発生します。プログラムの実行中に間違ったフォーマット文字列を暗黙的に変換しようとすると、エラーが発生します。

また、LINQ to SQLはエスケープシーケンスを自動的に処理して、アプリケーションをSQLインジェクションから保護します。

検証とビジネスロジックのルール


回路の検証は最初のステップとして役立ちますが、これでは十分ではありません。 多くの実際の状況では、検証のためのより詳細なアルゴリズムを定義する機能が必要です。これには、いくつかのプロパティ、特定のコードの実行、状態に関するモデルからの情報の取得などが含まれる場合があります(たとえば、ドメイン仕様に従って「アーカイブ済み」) 。 モデルクラスの検証ルールを定義および適用するために使用できるさまざまなパターンとフレームワークがあります。これらはすべて、.NETに基づくサードパーティのコンポーネントで実装されます。 ASP.NET MVCアプリケーションでそれらのいずれかを使用できます。

NerdDinnerアプリケーションの目標は、DinnerモデルオブジェクトのIsValidプロパティとGetRuleViolations()メソッドを拡張する、かなり単純で理解可能なパターンを使用することです。 IsValidプロパティは、検証とビジネスルールの成功に応じて、trueまたはfalseを返します。 GetRuleViolations()メソッドは、発生したエラーのリストを返します。

プロジェクトに部分クラスを追加して、DinnerモデルにIsValidおよびGetRuleViolations()を実装します。 部分クラスは、VSコンストラクター(LINQ to SQLコンストラクターによって生成されたDinnerクラスなど)によって制御されるクラスのメソッド/プロパティ/イベントを追加し、コードの混乱を回避するために使用されます。 \ Modelsフォルダを右クリックし、[新しい項目の追加]を選択して、プロジェクトに新しい部分クラスを追加できます。 次に、「クラス」テンプレートを選択し、Dinner.csという名前を付けます。



[追加]ボタンをクリックすると、Dinner.csファイルがプロジェクトに追加され、IDEで開きます。 次に、次のコードを使用して基本的なルールと検証フレームワークを定義します。
public partial class Dinner {

public bool IsValid {

get { return (GetRuleViolations().Count() == 0); }

}

public IEnumerable <RuleViolation> GetRuleViolations() {

yield break ;

}

partial void OnValidate(ChangeAction action) {

if (!IsValid)

throw new ApplicationException( "Rule violations prevent saving" );

}

}

public class RuleViolation {

public string ErrorMessage { get ; private set ; }

public string PropertyName { get ; private set ; }

public RuleViolation( string errorMessage, string propertyName) {

ErrorMessage = errorMessage;

PropertyName = propertyName;

}

}

コードに関するいくつかのメモ:Dinner.OnValidate()部分メソッドは、LINQ to SQLが提供するフックであり、Dinnerオブジェクトをデータベースに保存する準備ができたことをいつでも通知します。 OnValidate()の実装では、保存する前に、DinnerにRuleViolationが含まれていないことを確認します。 状態が有効でない場合、LINQ to SQLトランザクションを中止する例外がスローされます。

このアプローチは、検証ルールとビジネスルールを統合するシンプルなフレームワークを提供します。 次に、GetRuleViolations()メソッドにルールを追加しましょう。
public IEnumerable <RuleViolation> GetRuleViolations() {

if ( String .IsNullOrEmpty(Title))
yield return new RuleViolation( "Title required" , "Title" );

if ( String .IsNullOrEmpty(Description))
yield return new RuleViolation( "Description required" , "Description" );

if ( String .IsNullOrEmpty(HostedBy))
yield return new RuleViolation( "HostedBy required" , "HostedBy" );

if ( String .IsNullOrEmpty(Address))
yield return new RuleViolation( "Address required" , "Address" );

if ( String .IsNullOrEmpty(Country))
yield return new RuleViolation( "Country required" , "Country" );

if ( String .IsNullOrEmpty(ContactPhone))
yield return new RuleViolation( "Phone# required" , "ContactPhone" );

if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
yield return new RuleViolation( "Phone# does not match country" , "ContactPhone" );

yield break ;
}

RuleViolationsのシーケンスを返すには、「yield return」C#を使用します。 最初の6つのルールは、Dinnerの文字列プロパティをnullまたは空にできないことをテストおよび実施します。 最後のルールはより興味深いもので、PhoneValidator.IsValidNumber()ヘルパーメソッドを呼び出します。このメソッドをプロジェクトに追加して、ContactPhone番号の形式を夕食の場所の国に一致させることができます。

電話番号を確認するには、正規表現を使用することもできます。 PhoneValidatorの簡単な実装を次に示します。これにより、Regexを使用して電話を確認できます。
public class PhoneValidator {

static IDictionary< string , Regex> countryRegex = new Dictionary< string , Regex>() {
{ "USA" , new Regex( "^[2-9]\\d{2}-\\d{3}-\\d{4}$" )},
{ "UK" , new Regex( "(^1300\\d{6}$)|(^1800|1900|1902\\d{6}$)|(^0[2|3|7|8]{1}[0-9]{8}$)|(^13\\d{4}$)|(^04\\d{2,3}\\d{6}$)" )},
{ "Netherlands" , new Regex( "(^\\+[0-9]{2}|^\\+[0-9]{2}\\(0\\)|^\\(\\+[0-9]{2}\\)\\(0\\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\\-\\s]{10}$)" )},
};

public static bool IsValidNumber( string phoneNumber, string country) {

if (country != null && countryRegex.ContainsKey(country))
return countryRegex[country].IsMatch(phoneNumber);
else
return false ;
}

public static IEnumerable < string > Countries {
get {

return countryRegex.Keys;
}
}
}

検証違反とビジネスロジックの処理


検証ルールとビジネスロジックルールをコードに追加することにより、Dinnerオブジェクトを作成または変更するたびに、検証ルールが検証に強制的に関与します。

開発者は、以下に示すように、Dinnerオブジェクトが有効かどうかを事前に決定し、例外なくすべての違反のリストを受け取るコードを作成できます。
Dinner dinner = dinnerRepository.GetDinner(5);

dinner.Country = "USA" ;
dinner.ContactPhone = "425-555-BOGUS" ;

if (!dinner.IsValid) {
var errors = dinner.GetRuleViolations();
//
}

無効な状態でディナーを保存しようとすると、Save()メソッドを呼び出した後、DinnerRepositoryで例外がスローされます。 これは、LINQ to SQLが部分的なメソッドDinner.OnValidate()を変更が保存される前に呼び出すために発生します。Dinnerオブジェクトのルールに違反がある場合、以前にこのメソッドに例外を追加しました。 この例外をキャッチして、すぐに修正の違反リストを返すことができます。
Dinner dinner = dinnerRepository.GetDinner(5);

try {
dinner.Country = "USA" ;
dinner.ContactPhone = "425-555-BOGUS" ;
dinnerRepository.Save();
}
catch {
var errors = dinner.GetRuleViolations();
//
}

当社のビジネスルールと検証ルールは、GUIレベルではなくモデルレベルで実装され、アプリケーションのあらゆる状況で適用および使用されます。 後でビジネスルールを変更または追加し、アプリケーションの隅々に無条件に適用されるようにすることができます。

ビジネスルールを管理する柔軟性が得られ、使用されているアプリケーション全体でロジックを変更する必要がなくなったため、正しく記述されたアプリケーションを実現しました。 これはすべて、MVCフレームワークのおかげです。

次のステップ


データベース内のデータを取得および変更するために使用できるモデルがあります。

次に、プロジェクトにコントローラーとビューを追加します。

Source: https://habr.com/ru/post/J99107/


All Articles