Entity Framework 4.0 Database First と ASP.NET 4 Web Forms の概要 - 第 4 部
著者: Tom Dykstra
Contoso University のサンプル Web アプリケーションでは、Entity Framework 4.0 と Visual Studio 2010 を使用して ASP.NET Web Forms アプリケーションを作成する方法を示します。 チュートリアル シリーズについては、シリーズの最初のチュートリアルをご覧ください
関連データの操作
前のチュートリアルでは、EntityDataSource
コントロールを使用してデータのフィルター処理、並べ替え、グループ化を行いました。 このチュートリアルでは、関連データを表示および更新します。
インストラクタの一覧を表示する [インストラクター] ページを作成します。 インストラクターを選択すると、そのインストラクターによって教えられたコースの一覧が表示されます。 コースを選択すると、コースの詳細と、コースに登録されている学生の一覧が表示されます。 インストラクター名、採用日、オフィスの割り当てを編集できます。 オフィスの割り当ては、ナビゲーション プロパティを介してアクセスする個別のエンティティ セットです。
マークアップまたはコードで、マスター データを詳細データにリンクできます。 チュートリアルのこの部分では、両方のメソッドを使用します。
GridView コントロールでの関連エンティティの表示と更新
Site.Master マスター ページを使用する、Instructors.aspx という名前の新しい Web ページを作成し、Content2
という名前の Content
コントロールに次のマークアップを追加します:
<h2>Instructors</h2>
<div>
<asp:EntityDataSource ID="InstructorsEntityDataSource" runat="server"
ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False"
EntitySetName="People"
Where="it.HireDate is not null" Include="OfficeAssignment" EnableUpdate="True">
</asp:EntityDataSource>
</div>
このマークアップは、インストラクタを選択して更新を有効にするEntityDataSource
コントロールを作成します。 div
要素は、後で右側に列を追加できるように、左にレンダリングするようにマークアップを構成します。
EntityDataSource
マークアップと終了 </div>
タグの間に、エラー メッセージに使用する GridView
コントロールと Label
コントロールを作成する次のマークアップを追加します:
<asp:GridView ID="InstructorsGridView" runat="server" AllowPaging="True" AllowSorting="True"
AutoGenerateColumns="False" DataKeyNames="PersonID" DataSourceID="InstructorsEntityDataSource"
OnSelectedIndexChanged="InstructorsGridView_SelectedIndexChanged"
SelectedRowStyle-BackColor="LightGray"
onrowupdating="InstructorsGridView_RowUpdating">
<Columns>
<asp:CommandField ShowSelectButton="True" ShowEditButton="True" />
<asp:TemplateField HeaderText="Name" SortExpression="LastName">
<ItemTemplate>
<asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("LastName") %>'></asp:Label>,
<asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("FirstMidName") %>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="InstructorLastNameTextBox" runat="server" Text='<%# Bind("FirstMidName") %>' Width="7em"></asp:TextBox>
<asp:TextBox ID="InstructorFirstNameTextBox" runat="server" Text='<%# Bind("LastName") %>' Width="7em"></asp:TextBox>
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Hire Date" SortExpression="HireDate">
<ItemTemplate>
<asp:Label ID="InstructorHireDateLabel" runat="server" Text='<%# Eval("HireDate", "{0:d}") %>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="InstructorHireDateTextBox" runat="server" Text='<%# Bind("HireDate", "{0:d}") %>' Width="7em"></asp:TextBox>
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Office Assignment" SortExpression="OfficeAssignment.Location">
<ItemTemplate>
<asp:Label ID="InstructorOfficeLabel" runat="server" Text='<%# Eval("OfficeAssignment.Location") %>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="InstructorOfficeTextBox" runat="server"
Text='<%# Eval("OfficeAssignment.Location") %>' Width="7em"
oninit="InstructorOfficeTextBox_Init"></asp:TextBox>
</EditItemTemplate>
</asp:TemplateField>
</Columns>
<SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
</asp:GridView>
<asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false" ViewStateMode="Disabled"></asp:Label>
この GridView
コントロールは、行の選択を有効にし、選択した行を淡い灰色の背景色で強調表示し、SelectedIndexChanged
と Updating
イベントのハンドラー (後で作成します) を指定します。 また、選択した行のキー値を後で追加する別のコントロールに渡すことができるように、DataKeyNames
プロパティの PersonID
も指定します。
最後の列には、インストラクターのオフィスの割り当てが含まれています。この割り当ては、関連付けられたエンティティから取得されるため、Person
エンティティのナビゲーション プロパティに格納されます。 EditItemTemplate
要素は、Bind
ではなく Eval
を指定していることに注意してください。これは、GridView
コントロールを更新するためにナビゲーション プロパティに直接バインドできないためです。 コードでオフィスの割り当てを更新します。 そのためには、TextBox
コントロールへの参照が必要です。これを取得して、TextBox
コントロールの Init
イベントに保存します。
次の GridView
コントロールは、エラー メッセージに使用される Label
コントロールです。 コントロールの Visible
プロパティは false
で、ビューステートがオフになっているため、エラーに応答してコードが表示される場合にのみラベルが表示されます。
Instructors.aspx.cs ファイルを開き、次の using
ステートメントを追加します:
using ContosoUniversity.DAL;
部分クラス名宣言の直後にプライベート クラス フィールドを追加して、オフィスの割り当てテキスト ボックスへの参照を保持します。
private TextBox instructorOfficeTextBox;
後でコードを追加する SelectedIndexChanged
イベント ハンドラーのスタブを追加します。 また、TextBox
コントロールへの参照を格納できるように、イベント オフィス割り当て TextBox
コントロールの Init
ハンドラーを追加します。 この参照を使用して、ナビゲーション プロパティに関連付けられているエンティティを更新するためにユーザーが入力した値を取得します。
protected void InstructorsGridView_SelectedIndexChanged(object sender, EventArgs e)
{
}
protected void InstructorOfficeTextBox_Init(object sender, EventArgs e)
{
instructorOfficeTextBox = sender as TextBox;
}
GridView
コントロールの Updating
イベントを使用して、関連付けられている OfficeAssignment
エンティティの Location
プロパティを更新します。 Updating
イベントに次のハンドラーを追加します:
protected void InstructorsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
using (var context = new SchoolEntities())
{
var instructorBeingUpdated = Convert.ToInt32(e.Keys[0]);
var officeAssignment = (from o in context.OfficeAssignments
where o.InstructorID == instructorBeingUpdated
select o).FirstOrDefault();
try
{
if (String.IsNullOrWhiteSpace(instructorOfficeTextBox.Text) == false)
{
if (officeAssignment == null)
{
context.OfficeAssignments.AddObject(OfficeAssignment.CreateOfficeAssignment(instructorBeingUpdated, instructorOfficeTextBox.Text, null));
}
else
{
officeAssignment.Location = instructorOfficeTextBox.Text;
}
}
else
{
if (officeAssignment != null)
{
context.DeleteObject(officeAssignment);
}
}
context.SaveChanges();
}
catch (Exception)
{
e.Cancel = true;
ErrorMessageLabel.Visible = true;
ErrorMessageLabel.Text = "Update failed.";
//Add code to log the error.
}
}
}
このコードは、ユーザーが GridView
行 の [更新] をクリックしたときに実行されます。 このコードでは、LINQ to Entities を使用して、選択した行の PersonID
をイベント引数から使用して、現在の Person
エンティティに関連付けられている OfficeAssignment
エンティティを取得します。
その後、コードは、InstructorOfficeTextBox
コントロールの値に応じて、次のいずれかのアクションを実行します:
- テキスト ボックスに値があり、更新する
OfficeAssignment
エンティティがない場合は、その値が作成されます。 - テキスト ボックスに値があり、
OfficeAssignment
エンティティがある場合は、Location
プロパティ値が更新されます。 - テキスト ボックスが空で、
OfficeAssignment
エンティティが存在する場合は、エンティティが削除されます。
その後、変更がデータベースに保存されます。 例外が発生すると、エラー メッセージが表示されます。
このページを実行します。
[編集] をクリックすると、すべてのフィールドがテキスト ボックスに変更されます。
オフィスの割り当てを含め、これらの値のいずれかを変更します。 [更新] をクリックすると、変更が一覧に反映されます。
別のコントロールに関連エンティティを表示する
各インストラクターは 1 つ以上のコースを教えることができるので、 EntityDataSource
コントロールと GridView
コントロールを追加して、インストラクター GridView
コントロールで選択されているインストラクターに関連付けられているコースを一覧表示します。 コース エンティティの見出しと EntityDataSource
コントロールを作成するには、エラー メッセージ Label
コントロールと終了 </div>
タグの間に次のマークアップを追加します。
<h3>Courses Taught</h3>
<asp:EntityDataSource ID="CoursesEntityDataSource" runat="server"
ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False"
EntitySetName="Courses"
Where="@PersonID IN (SELECT VALUE instructor.PersonID FROM it.People AS instructor)">
<WhereParameters>
<asp:ControlParameter ControlID="InstructorsGridView" Type="Int32" Name="PersonID" PropertyName="SelectedValue" />
</WhereParameters>
</asp:EntityDataSource>
Where
パラメーターには、InstructorsGridView
コントロールで行が選択されているインストラクターの PersonID
の値が含まれます。 Where
プロパティには、Course
エンティティの People
ナビゲーション プロパティから関連付けられているすべての Person
エンティティを取得し、関連付けられている Person
エンティティの 1 つに選択した PersonID
値が含まれている場合にのみ、Course
エンティティを選択するサブセレクト コマンドが含まれています。
GridView
コントロールを作成するには、次のマークアップを CoursesEntityDataSource
コントロールのすぐ後に追加します (終了 </div>
タグの前):
<asp:GridView ID="CoursesGridView" runat="server"
DataSourceID="CoursesEntityDataSource"
AllowSorting="True" AutoGenerateColumns="False"
SelectedRowStyle-BackColor="LightGray"
DataKeyNames="CourseID">
<EmptyDataTemplate>
<p>No courses found.</p>
</EmptyDataTemplate>
<Columns>
<asp:CommandField ShowSelectButton="True" />
<asp:BoundField DataField="CourseID" HeaderText="ID" ReadOnly="True" SortExpression="CourseID" />
<asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
<asp:TemplateField HeaderText="Department" SortExpression="DepartmentID">
<ItemTemplate>
<asp:Label ID="GridViewDepartmentLabel" runat="server" Text='<%# Eval("Department.Name") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
インストラクターが選択されていない場合、コースは表示されないため、EmptyDataTemplate
要素が含まれます。
このページを実行します。
1 つまたは複数のコースが割り当てられているインストラクターを選択し、コースまたはコースが一覧に表示されます。 (注: データベース スキーマでは複数のコースが許可されますが、データベースで提供されるテスト データでは、インストラクターが実際に複数のコースを持っていません。[サーバー エクスプローラー] ウィンドウまたは CoursesAdd.aspx ページを使用して、データベースにコースを自分で追加できます。このページは、後のチュートリアルで追加します。)
CoursesGridView
コントロールには、いくつかのコース フィールドのみが表示されます。 コースのすべての詳細を表示するには、ユーザーが選択したコースの DetailsView
コントロールを使用します。 Instructors.aspxで、終了 </div>
タグの後に次のマークアップを追加します (このマークアップは、その前ではなく、終了 div タグの後にしてください):
<div>
<h3>Course Details</h3>
<asp:EntityDataSource ID="CourseDetailsEntityDataSource" runat="server"
ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False"
EntitySetName="Courses"
AutoGenerateWhereClause="False" Where="it.CourseID = @CourseID" Include="Department,OnlineCourse,OnsiteCourse,StudentGrades.Person"
OnSelected="CourseDetailsEntityDataSource_Selected">
<WhereParameters>
<asp:ControlParameter ControlID="CoursesGridView" Type="Int32" Name="CourseID" PropertyName="SelectedValue" />
</WhereParameters>
</asp:EntityDataSource>
<asp:DetailsView ID="CourseDetailsView" runat="server" AutoGenerateRows="False"
DataSourceID="CourseDetailsEntityDataSource">
<EmptyDataTemplate>
<p>
No course selected.</p>
</EmptyDataTemplate>
<Fields>
<asp:BoundField DataField="CourseID" HeaderText="ID" ReadOnly="True" SortExpression="CourseID" />
<asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
<asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" />
<asp:TemplateField HeaderText="Department">
<ItemTemplate>
<asp:Label ID="DetailsViewDepartmentLabel" runat="server" Text='<%# Eval("Department.Name") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Location">
<ItemTemplate>
<asp:Label ID="LocationLabel" runat="server" Text='<%# Eval("OnsiteCourse.Location") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="URL">
<ItemTemplate>
<asp:Label ID="URLLabel" runat="server" Text='<%# Eval("OnlineCourse.URL") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Fields>
</asp:DetailsView>
</div>
このマークアップは、Courses
エンティティ セットにバインドされた EntityDataSource
コントロールを作成します。 Where
プロパティは、コース GridView
コントロールで選択した行の CourseID
値を使用してコースを選択します。 マークアップでは、Selected
イベントのハンドラーを指定します。これは、後で学生の成績を表示するために使用します。これは、階層内のもう 1 つのレベルの低レベルです。
Instructors.aspx.csで、 CourseDetailsEntityDataSource_Selected
メソッドの次のスタブを作成します。 (このスタブはチュートリアルの後半で入力します。ここでは、ページがコンパイルされて実行されるように、このスタブを必要とします。)
protected void CourseDetailsEntityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
}
このページを実行します。
コースが選択されていないため、最初はコースの詳細はありません。 コースが割り当てられているインストラクターを選択し、コースを選択して詳細を表示します。
EntityDataSource "Selected" イベントを使用して関連データを表示する
最後に、選択したコースに登録されているすべての学生とその成績を表示します。 これを行うには、コース DetailsView
にバインドされた EntityDataSource
コントロールの Selected
イベントを使用します。
Instructors.aspxで、DetailsView
コントロールの後に次のマークアップを追加します:
<h3>Student Grades</h3>
<asp:ListView ID="GradesListView" runat="server">
<EmptyDataTemplate>
<p>No student grades found.</p>
</EmptyDataTemplate>
<LayoutTemplate>
<table border="1" runat="server" id="itemPlaceholderContainer">
<tr runat="server">
<th runat="server">
Name
</th>
<th runat="server">
Grade
</th>
</tr>
<tr id="itemPlaceholder" runat="server">
</tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>
<asp:Label ID="StudentLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>' />,
<asp:Label ID="StudentFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>' />
</td>
<td>
<asp:Label ID="StudentGradeLabel" runat="server" Text='<%# Eval("Grade") %>' />
</td>
</tr>
</ItemTemplate>
</asp:ListView>
このマークアップは、選択したコースの学生とその成績の一覧を表示する ListView
コントロールを作成します。 コードでコントロールをデータバインドするため、データ ソースは指定されません。 EmptyDataTemplate
要素は、その場合にコースが選択—されていないときに表示するメッセージを提供します。表示する学生はいません。 LayoutTemplate
要素は、リストを表示する HTML テーブルを作成し、ItemTemplate
は表示する列を指定します。 学生 ID と学生の成績は StudentGrade
エンティティから取得され、学生名は Entity Framework が StudentGrade
エンティティの Person
ナビゲーション プロパティで使用できる Person
エンティティからの名前です。
Instructors.aspx.csで、スタブアウト CourseDetailsEntityDataSource_Selected
メソッドを次のコードに置き換えます:
protected void CourseDetailsEntityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
var course = e.Results.Cast<Course>().FirstOrDefault();
if (course != null)
{
var studentGrades = course.StudentGrades.ToList();
GradesListView.DataSource = studentGrades;
GradesListView.DataBind();
}
}
このイベントのイベント引数は、選択したデータをコレクションの形式で提供します。何も選択されていない場合は 0 個の項目、Course
エンティティが選択されている場合は 1 つの項目が含まれます。 Course
エンティティが選択されている場合、コードは First
メソッドを使用してコレクションを 1 つのオブジェクトに変換します。 その後、ナビゲーション プロパティから StudentGrade
エンティティを取得し、それらをコレクションに変換し、GradesListView
コントロールをコレクションにバインドします。
これは成績を表示するのに十分ですが、空のデータ テンプレート内のメッセージが、ページが初めて表示されたとき、およびコースが選択されていないときは必ず表示されるようにする必要があります。 これを行うには、次のメソッドを作成します。このメソッドは 2 つの場所から呼び出します:
private void ClearStudentGradesDataSource()
{
var emptyStudentGradesList = new List<StudentGrade>();
GradesListView.DataSource = emptyStudentGradesList;
GradesListView.DataBind();
}
Page_Load
メソッドからこの新しいメソッドを呼び出して、ページが初めて表示されるときに空のデータ テンプレートを表示します。 また、InstructorsGridView_SelectedIndexChanged
メソッドから呼び出します。これは、インストラクターが選択されたときにイベントが発生するためです。これは、新しいコースがコントロール GridView
コースに読み込まれ、まだ選択されていないことを意味します。 2 つの呼び出しを次に示します:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ClearStudentGradesDataSource();
}
}
protected void InstructorsGridView_SelectedIndexChanged(object sender, EventArgs e)
{
ClearStudentGradesDataSource();
}
このページを実行します。
コースが割り当てられているインストラクターを選択し、コースを選択します。
これで、関連するデータを操作するいくつかの方法を確認しました。 次のチュートリアルでは、既存のエンティティ間のリレーションシップを追加する方法、リレーションシップを削除する方法、既存のエンティティにリレーションシップを持つ新しいエンティティを追加する方法について説明します。