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 FirstModel FirstCode 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 で動作するリポジトリ オブジェクトを指定します。 テスト用に、コレクションとして定義されたクラス変数など、簡単に操作できる方法で格納されたデータを操作するリポジトリ オブジェクトを指定します。

次の図は、リポジトリのないデータ アクセス ロジックを含むビジネス ロジック クラスと、リポジトリを使用するものの違いを示しています。

Image05

まず、基本的なデータ アクセス タスクのみを実行するため、ObjectDataSource コントロールをリポジトリに直接バインドする Web ページを作成します。 次のチュートリアルでは、検証ロジックを使用してビジネス ロジック クラスを作成し、リポジトリ クラスではなく、そのクラスに ObjectDataSource コントロールをバインドします。 検証ロジックの単体テストも作成します。 このシリーズの 3 番目のチュートリアルでは、並べ替えとフィルター処理の機能をアプリケーションに追加します。

このチュートリアルで作成するページは、概要チュートリアル シリーズで作成したデータ モデルの Departments エンティティ セットで動作します。

A screenshot that shows what your Departments page should look like.

Image02

データベースとデータ モデルの更新

このチュートリアルを開始するには、データベースに 2 つの変更を加えます。いずれも、Entity Framework と Web Forms の概要チュートリアルで作成したデータ モデルに対応する変更が必要です。 これらのチュートリアルのいずれかで、デザイナーで手動で変更を行い、データベースの変更後にデータ モデルをデータベースと同期しました。 このチュートリアルでは、デザイナーの [データベースからモデルを更新] ツールを使用して、データ モデルを自動的に更新します。

データベースへのリレーションシップの追加

Visual Studio で、Entity Framework と Web Forms の概要チュートリアル シリーズで作成した Contoso University Web アプリケーションを開いてから、SchoolDiagram データベース ダイアグラムを開きます。

データベース ダイアグラムの Department テーブルを見ると、Administrator 列が含まれていることがわかります。 この列は Person テーブルの外部キーですが、データベースに外部キーのリレーションシップは定義されていません。 リレーションシップを作成し、データ モデルを更新して、Entity Framework でこのリレーションシップを自動的に処理できるようにする必要があります。

データベース ダイアグラムで、Department テーブルを右クリックし、[リレーションシップ] を選択します。

Image80

[外部キーのリレーションシップ] ボックスで、[追加] をクリックし、[テーブルと列の指定] の省略記号をクリックします。

Image81

[テーブルと列] ダイアログ ボックスで、主キーのテーブルとフィールドを PersonPersonID に設定し、外部キーのテーブルとフィールドを DepartmentAdministrator に設定します (これを行うと、リレーションシップ名が FK_Department_Department から FK_Department_Person に変わります)。

Image82

[テーブルと列] ボックスで [OK] をクリックし、[外部キーのリレーションシップ] ボックスで [閉じる] クリックし、変更を保存します。 PersonDepartment テーブルを保存するかどうかを確認するメッセージが表示されたら、[はい] をクリックします。

Note

Administrator 列に既に含まれているデータに対応する Person 行を削除した場合、この変更を保存することはできません。 その場合は、サーバー エクスプローラーのテーブル エディターを使用して、すべての Department 行の Administrator 値に、Person テーブルに実際に存在するレコードの ID が含まれていることを確認します。

変更を保存した後、そのユーザーが部署管理者である場合、Person テーブルから行を削除することはできません。 運用アプリケーションでは、データベース制約によって削除が防止されている場合に特定のエラー メッセージを表示するか、連鎖削除を指定します。 連鎖削除を指定する方法の例については、Entity Framework と ASP.NET – 概要パート 2に関するページを参照してください。

データベースへのビューの追加

作成する新しい Departments.aspx ページで、ユーザーが部署管理者を選択できるように、"姓、名" 形式の名前のインストラクターのドロップダウン リストを指定したいと考えています。 これをより簡単に行うために、データベースにビューを作成します。 ビューは、ドロップダウン リストに必要なデータ (フルネーム (適切に書式設定されたもの) とレコード キー) だけで構成されます。

サーバー エクスプローラーで、School.mdf を展開し、Views フォルダーを右クリックし、[新しいビューの追加] を選択します。

Image06

[テーブルの追加] ダイアログ ボックスが表示されたら、[閉じる] をクリックし、次の SQL ステートメントを SQL ペインに貼り付けます。

SELECT        LastName + ',' + FirstName AS FullName, PersonID
FROM          dbo.Person
WHERE        (HireDate IS NOT NULL)

ビューを vInstructorName として保存します。

データ モデルの更新

DAL フォルダーで、SchoolModel.edmx ファイルを開き、デザイン画面を右クリックして、[データベースからモデルを更新] を選択します。

Image07

[データベース オブジェクトの選択] ダイアログ ボックスで、[追加] タブを選択し、先ほど作成したビューを選びます。

Image08

[完了] をクリックします。

デザイナーでは、ツールによって vInstructorName エンティティが作成され、DepartmentPerson エンティティ間の新しい関連付けが作成されていることがわかります。

Image13

Note

[出力][エラー一覧] ウィンドウに、新しい vInstructorName ビューの主キーがツールによって自動的に作成されたことを示す警告メッセージが表示される場合があります。 これは正しい動作です。

コードで新しい vInstructorName エンティティを参照する場合は、その前に小文字の "v" を付けるというデータベース規則を使用しません。 そのため、モデル内のエンティティとエンティティ セットの名前を変更します。

モデル ブラウザーを開きます。 vInstructorName がエンティティ型とビューとして一覧表示されます。

Image14

SchoolModel (SchoolModel.Store ではない) で、vInstructorName を右クリックし、[プロパティ] を選択します。 [プロパティ] ウィンドウで、Name プロパティを "InstructorName" に、Entity Set Name プロパティを "InstructorNames" に変更します。

Image15

データ モデルを保存して閉じ、プロジェクトをリビルドします。

リポジトリ クラスと 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 コントロールでは、EditDelete コマンドが指定されますが、まだそれらをサポートするコードを追加していません。

自動データの書式設定と検証機能を利用できるように、複数の列で 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 ページを実行します。

A screenshot that shows the Departments page when it has been run.

[予算] または [開始日] 列の 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 外部キー プロパティが更新されます。

ページを実行し、新しい部署の情報を追加し、[挿入] リンクをクリックします。

Image04

別の新しい部署の値を入力します。 [予算] フィールドに 1,000,000.00 より大きい数値を入力し、Tab キーを使用して次のフィールドに移動します。 フィールドにアスタリスクが表示され、その上にマウス ポインターを置くと、そのフィールドのメタデータに入力したエラー メッセージを表示できます。

Image03

[挿入] をクリックすると、ページの下部に ValidationSummary コントロールによってエラー メッセージが表示されます。

Image12

次に、ブラウザーを閉じて、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" >

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

A screenshot that shows the Departments page after it has been run.

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. のようなエラーを報告するエラー ページが表示されます

Image10

無効な予算額を入力し、[更新] をクリックすると、Departments.aspx ページに表示されたのと同じアスタリスクとエラー メッセージが示されます。

フィールドの値を変更するか、別の管理者を選択して [更新] をクリックします。 変更が表示されます。

A screenshot that shows the Departments page.

これで、Entity Framework での基本的な CRUD (作成、読み取り、更新、削除) 操作に対する ObjectDataSource コントロールの使用の概要は終わりです。 単純な n 層アプリケーションを構築しましたが、それでもビジネス ロジック レイヤーはデータ アクセス レイヤーと緊密に結合されているため、自動単体テストが複雑になります。 次のチュートリアルでは、単体テストを容易にするためにリポジトリ パターンを実装する方法を確認します。