Entity Framework 4.0 と ObjectDataSource コントロールの使用、パート 1: 概要
著者: Tom Dykstra
このチュートリアル シリーズは、Entity Framework 4.0 の概要チュートリアル シリーズで作成された Contoso University Web アプリケーションをベースとしています。 前のチュートリアルを終わらせていない場合は、このチュートリアルの始めに、前のチュートリアルで作成するはずであったアプリケーションをダウンロードできます。 完了したチュートリアル シリーズで作成されたアプリケーションをダウンロードすることもできます。
Contoso University のサンプル Web アプリケーションでは、Entity Framework 4.0 と Visual Studio 2010 を使用して ASP.NET Web Forms アプリケーションを作成する方法を示します。 サンプル アプリケーションは架空の Contoso University の Web サイトです。 学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれています。
このチュートリアルでは、C# の例を示します。 ダウンロード可能なサンプルには、C# と Visual Basic の両方のコードが含まれています。
Database First
Entity Framework では、Database First、Model First、Code Firstの 3 つの方法でデータを操作できます。 このチュートリアルは Database First 用です。 これらのワークフローの違いと、シナリオに最適なものを選択する方法に関するガイダンスについては、「Entity Framework 開発ワークフロー」を参照してください。
Web フォーム
概要シリーズと同様に、このチュートリアル シリーズでは、ASP.NET Web Forms モデルを使用し、Visual Studio で ASP.NET Web Forms を操作する方法を把握していることを前提としています。 そうでない場合は、ASP.NET 4.5 Web Forms の概要に関するページを参照してください。 ASP.NET MVC フレームワークを使う場合は、ASP.NET MVC を使用した Entity Framework の概要に関するページを参照してください。
ソフトウェア バージョン
チュートリアルで説明 以下でも動作可 Windows 7 Windows 8 Visual Studio 2010 Visual Studio 2010 Express for Web。 このチュートリアルは、新しいバージョンの Visual Studio ではテストされていません。 メニューの選択、ダイアログ ボックス、テンプレートには多くの違いがあります。 .NET 4 .NET 4.5 は .NET 4 と下位互換性がありますが、チュートリアルは .NET 4.5 ではテストされていません。 Entity Framework 4 このチュートリアルは、新しいバージョンの Entity Framework ではテストされていません。 Entity Framework 5 以降、EF では、EF 4.1 で導入された DbContext API
が既定で使用されます。 EntityDataSource コントロールは、ObjectContext
API を使用するように設計されています。DbContext
API で EntityDataSource コントロールを使用する方法については、こちらのブログ記事を参照してください。質問
チュートリアルに直接関連しない質問がある場合は、ASP.NET Entity Framework フォーラム、Entity Framework および LINQ to Entities フォーラム、または StackOverflow.com に投稿できます。
EntityDataSource
コントロールを使用すると、アプリケーションを非常にすばやく作成できますが、通常は、.aspx ページに大量のビジネス ロジックとデータ アクセス ロジックを保持する必要があります。 アプリケーションが複雑になり、継続的なメンテナンスが必要になると予想される場合は、メンテナンスしやすい "n 層" または "レイヤー化された" アプリケーション構造を作成するために事前に開発時間を増やすことができます。 このアーキテクチャを実装するには、プレゼンテーション レイヤーをビジネス ロジック レイヤー (BLL) とデータ アクセス レイヤー (DAL) から分離します。 この構造体を実装する 1 つの方法は、EntityDataSource
コントロールの代わりに ObjectDataSource
コントロールを使用することです。 ObjectDataSource
コントロールを使用する場合は、独自のデータ アクセス コードを実装し、他のデータ ソース コントロールと同じ機能の多くを持つコントロールを使って .aspx ページで呼び出します。 これにより、n 層アプローチの長所と、データ アクセスに Web Forms コントロールを使用する利点を組み合わせることができます。
ObjectDataSource
コントロールを使用すると、他の方法でも柔軟性が向上します。 独自のデータ アクセス コードを記述するため、EntityDataSource
コントロールが実行するように設計されているタスクである、特定のエンティティ型の読み取り、挿入、更新、または削除以上のことをより簡単に実行できます。 たとえば、エンティティが更新されるたびにログ記録を実行したり、エンティティが削除されるたびにデータをアーカイブしたり、外部キー値を持つ行を挿入するときに必要に応じて関連データを自動的に確認して更新したりすることができます。
ビジネス ロジックとリポジトリ クラス
ObjectDataSource
コントロールは、作成したクラスを呼び出すことによって機能します。 このクラスには、データを取得および更新するメソッドが含まれており、それらのメソッドの名前をマークアップの ObjectDataSource
コントロールに指定します。 レンダリングまたはポストバックの処理中に、ObjectDataSource
では、指定されたメソッドが呼び出されます。
基本的な CRUD 操作に加え、ObjectDataSource
コントロールで使用するために作成するクラスでは、ObjectDataSource
でのデータの読み取りや更新時にビジネス ロジックを実行する必要がある場合があります。 たとえば、部署を更新するときに、1 人のユーザーが複数の部署の管理者になることができないため、同じ管理者が他の部署にいないことを検証する必要がある場合があります。
ObjectDataSource クラスの概要など、一部の ObjectDataSource
ドキュメントでは、ビジネス ロジックとデータ アクセス ロジックの両方を含む "ビジネス オブジェクト" と呼ばれるクラスがコントロールによって呼び出されます。 このチュートリアルでは、ビジネス ロジックとデータ アクセス ロジック用に個別のクラスを作成します。 データ アクセス ロジックをカプセル化するクラスは、"リポジトリ" と呼ばれます。 ビジネス ロジック クラスにはビジネス ロジック メソッドとデータ アクセス メソッドの両方が含まれますが、データ アクセス メソッドではリポジトリを呼び出してデータ アクセス タスクを実行します。
また、BLL と DAL の間に抽象化レイヤーを作成し、BLL の自動単体テストを容易にします。 この抽象化レイヤーは、インターフェイスを作成し、ビジネス ロジック クラスでリポジトリをインスタンス化するときにインターフェイスを使用して実装されます。 これにより、リポジトリ インターフェイスを実装する任意のオブジェクトへの参照をビジネス ロジック クラスに提供できます。 通常の操作では、Entity Framework で動作するリポジトリ オブジェクトを指定します。 テスト用に、コレクションとして定義されたクラス変数など、簡単に操作できる方法で格納されたデータを操作するリポジトリ オブジェクトを指定します。
次の図は、リポジトリのないデータ アクセス ロジックを含むビジネス ロジック クラスと、リポジトリを使用するものの違いを示しています。
まず、基本的なデータ アクセス タスクのみを実行するため、ObjectDataSource
コントロールをリポジトリに直接バインドする Web ページを作成します。 次のチュートリアルでは、検証ロジックを使用してビジネス ロジック クラスを作成し、リポジトリ クラスではなく、そのクラスに ObjectDataSource
コントロールをバインドします。 検証ロジックの単体テストも作成します。 このシリーズの 3 番目のチュートリアルでは、並べ替えとフィルター処理の機能をアプリケーションに追加します。
このチュートリアルで作成するページは、概要チュートリアル シリーズで作成したデータ モデルの Departments
エンティティ セットで動作します。
データベースとデータ モデルの更新
このチュートリアルを開始するには、データベースに 2 つの変更を加えます。いずれも、Entity Framework と Web Forms の概要チュートリアルで作成したデータ モデルに対応する変更が必要です。 これらのチュートリアルのいずれかで、デザイナーで手動で変更を行い、データベースの変更後にデータ モデルをデータベースと同期しました。 このチュートリアルでは、デザイナーの [データベースからモデルを更新] ツールを使用して、データ モデルを自動的に更新します。
データベースへのリレーションシップの追加
Visual Studio で、Entity Framework と Web Forms の概要チュートリアル シリーズで作成した Contoso University Web アプリケーションを開いてから、SchoolDiagram
データベース ダイアグラムを開きます。
データベース ダイアグラムの Department
テーブルを見ると、Administrator
列が含まれていることがわかります。 この列は Person
テーブルの外部キーですが、データベースに外部キーのリレーションシップは定義されていません。 リレーションシップを作成し、データ モデルを更新して、Entity Framework でこのリレーションシップを自動的に処理できるようにする必要があります。
データベース ダイアグラムで、Department
テーブルを右クリックし、[リレーションシップ] を選択します。
[外部キーのリレーションシップ] ボックスで、[追加] をクリックし、[テーブルと列の指定] の省略記号をクリックします。
[テーブルと列] ダイアログ ボックスで、主キーのテーブルとフィールドを Person
と PersonID
に設定し、外部キーのテーブルとフィールドを Department
と Administrator
に設定します (これを行うと、リレーションシップ名が FK_Department_Department
から FK_Department_Person
に変わります)。
[テーブルと列] ボックスで [OK] をクリックし、[外部キーのリレーションシップ] ボックスで [閉じる] クリックし、変更を保存します。 Person
と Department
テーブルを保存するかどうかを確認するメッセージが表示されたら、[はい] をクリックします。
Note
Administrator
列に既に含まれているデータに対応する Person
行を削除した場合、この変更を保存することはできません。 その場合は、サーバー エクスプローラーのテーブル エディターを使用して、すべての Department
行の Administrator
値に、Person
テーブルに実際に存在するレコードの ID が含まれていることを確認します。
変更を保存した後、そのユーザーが部署管理者である場合、Person
テーブルから行を削除することはできません。 運用アプリケーションでは、データベース制約によって削除が防止されている場合に特定のエラー メッセージを表示するか、連鎖削除を指定します。 連鎖削除を指定する方法の例については、Entity Framework と ASP.NET – 概要パート 2に関するページを参照してください。
データベースへのビューの追加
作成する新しい Departments.aspx ページで、ユーザーが部署管理者を選択できるように、"姓、名" 形式の名前のインストラクターのドロップダウン リストを指定したいと考えています。 これをより簡単に行うために、データベースにビューを作成します。 ビューは、ドロップダウン リストに必要なデータ (フルネーム (適切に書式設定されたもの) とレコード キー) だけで構成されます。
サーバー エクスプローラーで、School.mdf を展開し、Views フォルダーを右クリックし、[新しいビューの追加] を選択します。
[テーブルの追加] ダイアログ ボックスが表示されたら、[閉じる] をクリックし、次の SQL ステートメントを SQL ペインに貼り付けます。
SELECT LastName + ',' + FirstName AS FullName, PersonID
FROM dbo.Person
WHERE (HireDate IS NOT NULL)
ビューを vInstructorName
として保存します。
データ モデルの更新
DAL フォルダーで、SchoolModel.edmx ファイルを開き、デザイン画面を右クリックして、[データベースからモデルを更新] を選択します。
[データベース オブジェクトの選択] ダイアログ ボックスで、[追加] タブを選択し、先ほど作成したビューを選びます。
[完了] をクリックします。
デザイナーでは、ツールによって vInstructorName
エンティティが作成され、Department
と Person
エンティティ間の新しい関連付けが作成されていることがわかります。
Note
[出力] と [エラー一覧] ウィンドウに、新しい vInstructorName
ビューの主キーがツールによって自動的に作成されたことを示す警告メッセージが表示される場合があります。 これは正しい動作です。
コードで新しい vInstructorName
エンティティを参照する場合は、その前に小文字の "v" を付けるというデータベース規則を使用しません。 そのため、モデル内のエンティティとエンティティ セットの名前を変更します。
モデル ブラウザーを開きます。 vInstructorName
がエンティティ型とビューとして一覧表示されます。
SchoolModel (SchoolModel.Store ではない) で、vInstructorName を右クリックし、[プロパティ] を選択します。 [プロパティ] ウィンドウで、Name プロパティを "InstructorName" に、Entity Set Name プロパティを "InstructorNames" に変更します。
データ モデルを保存して閉じ、プロジェクトをリビルドします。
リポジトリ クラスと ObjectDataSource コントロールの使用
DAL フォルダーに新しいクラス ファイルを作成し、SchoolRepository.cs という名前を付け、既存のコードを次のコードに置き換えます。
using System;
using System.Collections.Generic;
using System.Linq;
using ContosoUniversity.DAL;
namespace ContosoUniversity.DAL
{
public class SchoolRepository : IDisposable
{
private SchoolEntities context = new SchoolEntities();
public IEnumerable<Department> GetDepartments()
{
return context.Departments.Include("Person").ToList();
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
context.Dispose();
}
}
this.disposedValue = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
このコードでは、Departments
エンティティ セット内のすべてのエンティティを返す 1 つの GetDepartments
メソッドが提供されます。 返されるすべての行の Person
ナビゲーション プロパティにアクセスすることがわかっているため、Include
メソッドを使用してそのプロパティの一括読み込みを指定します。 クラスでは、オブジェクトが破棄されたときに確実にデータベース接続が解放されるように、IDisposable
インターフェイスも実装されます。
Note
一般的な方法は、エンティティ型ごとにリポジトリ クラスを作成することです。 このチュートリアルでは、複数のエンティティ型に対して 1 つのリポジトリ クラスを使用します。 リポジトリ パターンの詳細については、Entity Framework チームのブログと Julie Lerman のブログを参照してください。
GetDepartments
メソッドでは、リポジトリ オブジェクト自体が破棄された後でも、返されたコレクションを確実に使用できるようにするために、IQueryable
オブジェクトではなく、IEnumerable
オブジェクトが返されます。 IQueryable
オブジェクトは、アクセスされるたびにデータベース アクセスを引き起こす可能性がありますが、データバインド コントロールでデータのレンダリングが試行されるまでにリポジトリ オブジェクトが破棄される可能性があります。 IEnumerable
オブジェクトではなく、IList
オブジェクトなど、別のコレクション型を返すことができます。 しかし、IEnumerable
オブジェクトを返すと、foreach
ループや LINQ クエリなどの一般的な読み取り専用リスト処理タスクを確実に実行できますが、コレクション内の項目の追加や削除はできません。これは、そのような変更がデータベースに保持されることを意味する可能性があります。
Site.Master マスター ページを使用する Departments.aspx ページを作成し、Content2
という名前の Content
コントロールに次のマークアップを追加します。
<h2>Departments</h2>
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartments" >
</asp:ObjectDataSource>
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource" >
<Columns>
<asp:CommandField ShowEditButton="True" ShowDeleteButton="True"
ItemStyle-VerticalAlign="Top">
</asp:CommandField>
<asp:DynamicField DataField="Name" HeaderText="Name" SortExpression="Name" ItemStyle-VerticalAlign="Top" />
<asp:DynamicField DataField="Budget" HeaderText="Budget" SortExpression="Budget" ItemStyle-VerticalAlign="Top" />
<asp:DynamicField DataField="StartDate" HeaderText="Start Date" ItemStyle-VerticalAlign="Top" />
<asp:TemplateField HeaderText="Administrator" SortExpression="Person.LastName" ItemStyle-VerticalAlign="Top" >
<ItemTemplate>
<asp:Label ID="AdministratorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
<asp:Label ID="AdministratorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
このマークアップにより、先ほど作成したリポジトリ クラスを使用する ObjectDataSource
コントロールと、データを表示するための GridView
コントロールが作成されます。 GridView
コントロールでは、Edit と Delete コマンドが指定されますが、まだそれらをサポートするコードを追加していません。
自動データの書式設定と検証機能を利用できるように、複数の列で DynamicField
コントロールが使用されます。 これらを機能させるには、Page_Init
イベント ハンドラーで EnableDynamicData
メソッドを呼び出す必要があります (DynamicControl
コントロールは、ナビゲーション プロパティでは機能しないため、Administrator
フィールドでは使用されません)。
入れ子になった GridView
コントロールを持つ列を後でグリッドに追加するときに、Vertical-Align="Top"
属性が重要になります。
Departments.aspx.cs ファイルを開き、次の using
ステートメントを追加します。
using ContosoUniversity.DAL;
その後、ページの Init
イベントに対して次のハンドラーを追加します。
protected void Page_Init(object sender, EventArgs e)
{
DepartmentsGridView.EnableDynamicData(typeof(Department));
}
DAL フォルダーで、Department.cs という名前の新しいクラス ファイルを作成し、既存のコードを次のコードに置き換えます。
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.DAL
{
[MetadataType(typeof(DepartmentMetaData))]
public partial class Department
{
}
public class DepartmentMetaData
{
[DataType(DataType.Currency)]
[Range(0, 1000000, ErrorMessage = "Budget must be less than $1,000,000.00")]
public Decimal Budget { get; set; }
[DisplayFormat(DataFormatString="{0:d}",ApplyFormatInEditMode=true)]
public DateTime StartDate { get; set; }
}
}
このコードでは、メタデータがデータ モデルに追加されます。 Department
エンティティの Budget
プロパティは、実際には通貨を表しますが、そのデータ型は Decimal
であることが指定され、値は 0 から $1,000,000.00 の間である必要があることが指定されます。 また、StartDate
プロパティを mm/dd/yyyy 形式の日付として書式設定する必要があることも指定されます。
Departments.aspx ページを実行します。
[予算] または [開始日] 列の Departments.aspx ページ マークアップで書式文字列を指定しなかったにもかかわらず、Department.cs ファイルで指定したメタデータを使用して、既定の通貨と日付の書式設定が DynamicField
コントロールによって適用されていることに注目してください。
挿入と削除の機能の追加
SchoolRepository.cs を開き、Insert
メソッドと Delete
メソッドを作成するために次のコードを追加します。 このコードには、Insert
メソッドで使用できる次のレコード キー値を計算する GenerateDepartmentID
という名前のメソッドも含まれています。 これは、Department
テーブルに対してこれを自動的に計算するようにデータベースが構成されていないために必要です。
public void InsertDepartment(Department department)
{
try
{
department.DepartmentID = GenerateDepartmentID();
context.Departments.AddObject(department);
context.SaveChanges();
}
catch (Exception ex)
{
//Include catch blocks for specific exceptions first,
//and handle or log the error as appropriate in each.
//Include a generic catch block like this one last.
throw ex;
}
}
public void DeleteDepartment(Department department)
{
try
{
context.Departments.Attach(department);
context.Departments.DeleteObject(department);
context.SaveChanges();
}
catch (Exception ex)
{
//Include catch blocks for specific exceptions first,
//and handle or log the error as appropriate in each.
//Include a generic catch block like this one last.
throw ex;
}
}
private Int32 GenerateDepartmentID()
{
Int32 maxDepartmentID = 0;
var department = (from d in GetDepartments()
orderby d.DepartmentID descending
select d).FirstOrDefault();
if (department != null)
{
maxDepartmentID = department.DepartmentID + 1;
}
return maxDepartmentID;
}
Attach メソッド
DeleteDepartment
メソッドでは、メモリ内のエンティティとそれが表すデータベース行の間のオブジェクト コンテキストのオブジェクト状態マネージャーで保持されているリンクを再確立するために、Attach
メソッドが呼び出されます。 これは、そのメソッドで SaveChanges
メソッドが呼び出される前に行われる必要があります。
"オブジェクト コンテキスト" という用語は、エンティティ セットとエンティティにアクセスするために使用する ObjectContext
クラスから派生する Entity Framework クラスを指します。 このプロジェクトのコードでは、クラスには SchoolEntities
という名前が付けられ、そのインスタンスには常に context
という名前が付けられます。 オブジェクト コンテキストの "オブジェクト状態マネージャー" は、ObjectStateManager
クラスから派生するクラスです。 オブジェクト接触ではオブジェクト状態マネージャーを使用して、エンティティ オブジェクトが格納され、それぞれがデータベース内の対応するテーブル行と同期しているかどうかが追跡されます。
エンティティを読み取ると、オブジェクト コンテキストによってそれがオブジェクト状態マネージャーに格納され、そのオブジェクトの表現がデータベースと同期しているかどうかが追跡されます。 たとえば、プロパティ値を変更すると、変更したプロパティがデータベースと同期しなくなったことを示すフラグが設定されます。 その後、SaveChanges
メソッドを呼び出したときに、オブジェクト コンテキストにより、データベースで行われる内容が認識されます。これは、オブジェクト状態マネージャーにより、エンティティの現在の状態とデータベースの状態の違いが正確に認識されるためです。
しかし、このプロセスは通常、Web アプリケーションでは機能しません。これは、エンティティを読み取るオブジェクト コンテキスト インスタンスと、そのオブジェクト状態マネージャー内のすべてのものが、ページがレンダリングされた後に破棄されるためです。 変更を適用する必要があるオブジェクト コンテキスト インスタンスは、ポストバック処理用にインスタンス化された新しいものです。 DeleteDepartment
メソッドの場合、ObjectDataSource
コントロールによって自動的にビュー状態の値からエンティティの元のバージョンが再作成されますが、この再作成された Department
エンティティはオブジェクト状態マネージャーに存在しません。 この再作成されたエンティティで DeleteObject
メソッドを呼び出した場合、オブジェクト コンテキストでエンティティがデータベースと同期しているかどうかが認識されないため、呼び出しは失敗します。 しかし、Attach
メソッドを呼び出すと、再作成されたエンティティと、オブジェクト コンテキストの以前のインスタンスでエンティティが読み取られたときに最初に自動的に実行されたデータベース内の値との間で同じ追跡が再確立されます。
オブジェクト コンテキストでオブジェクト状態マネージャーのエンティティを追跡したくない場合があります。その場合は、フラグを設定してそれを防ぐことができます。 この例は、このシリーズの後のチュートリアルで示します。
SaveChanges メソッド
この単純なリポジトリ クラスでは、CRUD 操作を実行する方法の基本原則が示されています。 この例では、各更新の直後に SaveChanges
メソッドが呼び出されます。 運用アプリケーションでは、別のメソッドから SaveChanges
メソッドを呼び出して、データベースが更新されるタイミングをより詳細に制御できます (次のチュートリアルの最後には、関連する更新を調整するための 1 つのアプローチである作業単位パターンについて説明するホワイト ペーパーへのリンクがあります)。また、この例では、コンカレンシーの競合を処理するためのコードが DeleteDepartment
メソッドに含まれていないことに注目してください。これを行うコードは、このシリーズの後のチュートリアルで追加されます。
挿入時に選択するインストラクター名の取得
ユーザーは、新しい部署を作成するときに、ドロップダウン リストのインストラクターのリストから管理者を選択できる必要があります。 そのため、前に作成したビューを使用してインストラクターのリストを取得するメソッドを作成するために SchoolRepository.cs に次のコードを追加します。
public IEnumerable<InstructorName> GetInstructorNames()
{
return context.InstructorNames.OrderBy("it.FullName").ToList();
}
部署を挿入するためのページの作成
Site.Master ページを使用する DepartmentsAdd.aspx ページを作成し、Content2
という名前の Content
コントロールに次のマークアップを追加します。
<h2>Departments</h2>
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository" DataObjectTypeName="ContosoUniversity.DAL.Department"
InsertMethod="InsertDepartment" >
</asp:ObjectDataSource>
<asp:DetailsView ID="DepartmentsDetailsView" runat="server"
DataSourceID="DepartmentsObjectDataSource" AutoGenerateRows="False"
DefaultMode="Insert" OnItemInserting="DepartmentsDetailsView_ItemInserting">
<Fields>
<asp:DynamicField DataField="Name" HeaderText="Name" />
<asp:DynamicField DataField="Budget" HeaderText="Budget" />
<asp:DynamicField DataField="StartDate" HeaderText="Start Date" />
<asp:TemplateField HeaderText="Administrator">
<InsertItemTemplate>
<asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
SelectMethod="GetInstructorNames" >
</asp:ObjectDataSource>
<asp:DropDownList ID="InstructorsDropDownList" runat="server"
DataSourceID="InstructorsObjectDataSource"
DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init">
</asp:DropDownList>
</InsertItemTemplate>
</asp:TemplateField>
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server"
ShowSummary="true" DisplayMode="BulletList" />
このマークアップでは、2 つの ObjectDataSource
コントロールが作成されます。1 つは新しい Department
エンティティを挿入するためのもので、もう 1 つは部署管理者の選択に使用される DropDownList
コントロールのインストラクター名を取得するためのものです。 マークアップでは、新しい部署を入力するための DetailsView
コントロールが作成され、コントロールの ItemInserting
イベントのハンドラーが指定され、Administrator
外部キー値を設定できるようにします。 最後は、エラー メッセージを表示するための ValidationSummary
コントロールです。
DepartmentsAdd.aspx.cs を開き、次の using
ステートメントを追加します。
using ContosoUniversity.DAL;
次のクラス変数とメソッドを追加します。
private DropDownList administratorsDropDownList;
protected void Page_Init(object sender, EventArgs e)
{
DepartmentsDetailsView.EnableDynamicData(typeof(Department));
}
protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
administratorsDropDownList = sender as DropDownList;
}
protected void DepartmentsDetailsView_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
e.Values["Administrator"] = administratorsDropDownList.SelectedValue;
}
Page_Init
メソッドにより、動的データ機能が有効になります。 DropDownList
コントロールの Init
イベントのハンドラーではコントロールへの参照が保存され、DetailsView
コントロールの Inserting
イベントのハンドラーではその参照を使用して、選択されたインストラクターの PersonID
値が取得され、Department
エンティティの Administrator
外部キー プロパティが更新されます。
ページを実行し、新しい部署の情報を追加し、[挿入] リンクをクリックします。
別の新しい部署の値を入力します。 [予算] フィールドに 1,000,000.00 より大きい数値を入力し、Tab キーを使用して次のフィールドに移動します。 フィールドにアスタリスクが表示され、その上にマウス ポインターを置くと、そのフィールドのメタデータに入力したエラー メッセージを表示できます。
[挿入] をクリックすると、ページの下部に ValidationSummary
コントロールによってエラー メッセージが表示されます。
次に、ブラウザーを閉じて、Departments.aspx ページを開きます。 ObjectDataSource
コントロールに DeleteMethod
属性を、GridView
コントロールには DataKeyNames
属性を追加して、Departments.aspx ページに削除機能を追加します。 これらのコントロールの開始タグは、次の例のようになります。
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartments"
DeleteMethod="DeleteDepartment" >
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID" >
このページを実行します。
DepartmentsAdd.aspx ページの実行時に追加した部署を削除します。
更新機能の追加
SchoolRepository.cs を開き、次の Update
メソッドを追加します。
public void UpdateDepartment(Department department, Department origDepartment)
{
try
{
context.Departments.Attach(origDepartment);
context.ApplyCurrentValues("Departments", department);
context.SaveChanges();
}
catch (Exception ex)
{
//Include catch blocks for specific exceptions first,
//and handle or log the error as appropriate in each.
//Include a generic catch block like this one last.
throw ex;
}
}
Departments.aspx ページで [更新] をクリックすると、ObjectDataSource
コントロールによって、UpdateDepartment
メソッドに渡す 2 つの Department
エンティティが作成されます。 1 つにはビュー状態で格納されている元の値が含まれ、もう 1 つには GridView
コントロールに入力された新しい値が含まれます。 UpdateDepartment
メソッドのコードでは、元の値を持つ Department
エンティティが Attach
メソッドに渡され、エンティティとデータベース内のものの間の追跡が確立されます。 その後、コードでは、新しい値を持つ Department
エンティティが ApplyCurrentValues
メソッドに渡されます。 オブジェクト コンテキストでは、古い値と新しい値が比較されます。 新しい値が古い値と異なる場合、オブジェクト コンテキストによってプロパティ値が変更されます。 その後、SaveChanges
メソッドでは、データベース内の変更された列のみが更新されます (ただし、このエンティティの更新関数がストアド プロシージャにマップされている場合、変更された列に関係なく行全体が更新されます)。
Departments.aspx ファイルを開き、DepartmentsObjectDataSource
コントロールに次の属性を追加します。
UpdateMethod="UpdateDepartment"
ConflictDetection="CompareAllValues"
これにより、古い値がビュー状態で格納され、Update
メソッドの新しい値と比較できるようになります。OldValuesParameterFormatString="orig{0}"
これにより、元の値パラメーターの名前がorigDepartment
であることがコントロールに伝えられます。
ObjectDataSource
コントロールの開始タグのマークアップは、次の例のようになります。
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartments" DeleteMethod="DeleteDepartment"
UpdateMethod="UpdateDepartment"
ConflictDetection="CompareAllValues"
OldValuesParameterFormatString="orig{0}" >
OnRowUpdating="DepartmentsGridView_RowUpdating"
属性を GridView
コントロールに追加します。 これを使用して、ユーザーがドロップダウン リストで選択した行に基づいて Administrator
プロパティ値を設定します。 GridView
開始タグは、次の例のようになります。
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID"
OnRowUpdating="DepartmentsGridView_RowUpdating">
Administrator
列の EditItemTemplate
コントロールを、その列の ItemTemplate
コントロールの直後の GridView
コントロールに追加します。
<EditItemTemplate>
<asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
SelectMethod="GetInstructorNames" TypeName="ContosoUniversity.DAL.SchoolRepository">
</asp:ObjectDataSource>
<asp:DropDownList ID="InstructorsDropDownList" runat="server" DataSourceID="InstructorsObjectDataSource"
SelectedValue='<%# Eval("Administrator") %>'
DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init" >
</asp:DropDownList>
</EditItemTemplate>
この EditItemTemplate
コントロールは、DepartmentsAdd.aspx ページの InsertItemTemplate
コントロールに似ています。 違いは、SelectedValue
属性を使用してコントロールの初期値が設定されていることです。
GridView
コントロールの前に、DepartmentsAdd.aspx ページで行ったように、ValidationSummary
コントロールを追加します。
<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server"
ShowSummary="true" DisplayMode="BulletList" />
Departments.aspx.cs を開き、部分クラス宣言の直後に、次のコードを追加して、DropDownList
コントロールを参照するプライベート フィールドを作成します。
private DropDownList administratorsDropDownList;
その後、DropDownList
コントロールの Init
イベントと GridView
コントロールの RowUpdating
イベントのハンドラーを追加します。
protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
administratorsDropDownList = sender as DropDownList;
}
protected void DepartmentsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
e.NewValues["Administrator"] = administratorsDropDownList.SelectedValue;
}
Init
イベントのハンドラーでは、クラス フィールドの DropDownList
コントロールへの参照が保存されます。 RowUpdating
イベントのハンドラーではその参照を使用して、ユーザーが入力した値が取得され、Department
エンティティの Administrator
プロパティに適用されます。
DepartmentsAdd.aspx ページを使用して新しい部署を追加し、Departments.aspx ページを実行して、追加した行の [編集] をクリックします。
Note
データベース内のデータが無効なため、追加しなかった (つまり、データベースに既に存在していた) 行を編集することはできません。データベースで作成された行の管理者は学生です。 そのうちの 1 つを編集しようとすると、'InstructorsDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.
のようなエラーを報告するエラー ページが表示されます
無効な予算額を入力し、[更新] をクリックすると、Departments.aspx ページに表示されたのと同じアスタリスクとエラー メッセージが示されます。
フィールドの値を変更するか、別の管理者を選択して [更新] をクリックします。 変更が表示されます。
これで、Entity Framework での基本的な CRUD (作成、読み取り、更新、削除) 操作に対する ObjectDataSource
コントロールの使用の概要は終わりです。 単純な n 層アプリケーションを構築しましたが、それでもビジネス ロジック レイヤーはデータ アクセス レイヤーと緊密に結合されているため、自動単体テストが複雑になります。 次のチュートリアルでは、単体テストを容易にするためにリポジトリ パターンを実装する方法を確認します。