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 コントロールを使用してデータのフィルター処理、並べ替え、グループ化を行いました。 このチュートリアルでは、関連データを表示および更新します。

インストラクタの一覧を表示する [インストラクター] ページを作成します。 インストラクターを選択すると、そのインストラクターによって教えられたコースの一覧が表示されます。 コースを選択すると、コースの詳細と、コースに登録されている学生の一覧が表示されます。 インストラクター名、採用日、オフィスの割り当てを編集できます。 オフィスの割り当ては、ナビゲーション プロパティを介してアクセスする個別のエンティティ セットです。

マークアップまたはコードで、マスター データを詳細データにリンクできます。 チュートリアルのこの部分では、両方のメソッドを使用します。

Image01

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 コントロールは、行の選択を有効にし、選択した行を淡い灰色の背景色で強調表示し、SelectedIndexChangedUpdating イベントのハンドラー (後で作成します) を指定します。 また、選択した行のキー値を後で追加する別のコントロールに渡すことができるように、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 エンティティが存在する場合は、エンティティが削除されます。

その後、変更がデータベースに保存されます。 例外が発生すると、エラー メッセージが表示されます。

このページを実行します。

Image02

[編集] をクリックすると、すべてのフィールドがテキスト ボックスに変更されます。

Image03

オフィスの割り当てを含め、これらの値のいずれかを変更します。 [更新] をクリックすると、変更が一覧に反映されます。

各インストラクターは 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 要素が含まれます。

このページを実行します。

Image04

1 つまたは複数のコースが割り当てられているインストラクターを選択し、コースまたはコースが一覧に表示されます。 (注: データベース スキーマでは複数のコースが許可されますが、データベースで提供されるテスト データでは、インストラクターが実際に複数のコースを持っていません。[サーバー エクスプローラー] ウィンドウまたは CoursesAdd.aspx ページを使用して、データベースにコースを自分で追加できます。このページは、後のチュートリアルで追加します。)

Image05

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)
{
}

このページを実行します。

Image06

コースが選択されていないため、最初はコースの詳細はありません。 コースが割り当てられているインストラクターを選択し、コースを選択して詳細を表示します。

Image07

最後に、選択したコースに登録されているすべての学生とその成績を表示します。 これを行うには、コース 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();
}

このページを実行します。

Image08

コースが割り当てられているインストラクターを選択し、コースを選択します。

Image09

これで、関連するデータを操作するいくつかの方法を確認しました。 次のチュートリアルでは、既存のエンティティ間のリレーションシップを追加する方法、リレーションシップを削除する方法、既存のエンティティにリレーションシップを持つ新しいエンティティを追加する方法について説明します。