次の方法で共有


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 フォーム アプリケーションを作成する方法を示します。 サンプル アプリケーションは、架空の Contoso University の Web サイトです。 学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれています。

このチュートリアルでは、C# の例を示します。 ダウンロード可能なサンプルには、C# と Visual Basic の両方のコードが含まれています。

Database First

Entity Framework では、Database FirstModel FirstCode Firstの 3 つの方法でデータを操作できます。 このチュートリアルは、Database First を対象にしています。 これらのワークフローの違いと、シナリオに最適なものを選択する方法に関するガイダンスについては、「Entity Framework 開発ワークフロー」を参照してください。

Web Forms

入門シリーズと同様に、このチュートリアル シリーズでは、ASP.NET Web フォーム モデルを使用し、Visual Studio で web フォーム ASP.NET 操作する方法を把握していることを前提としています。 そうでない場合は、「ASP.NET 4.5 Web フォーム入門」を参照してください。 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 つの方法は、ObjectDataSource コントロールの代わりにEntityDataSource コントロールを使用することです。 ObjectDataSource コントロールを使用する場合は、独自のデータ アクセス コードを実装し、他のデータ ソース コントロールと同じ機能の多くを持つコントロールを使用して、.aspx ページで呼び出します。 これにより、n 層アプローチの利点と、データ アクセスに Web フォーム コントロールを使用する利点を組み合わせることができます。

ObjectDataSource コントロールを使用すると、他の方法でも柔軟性が向上します。 独自のデータ アクセス コードを記述するため、特定のエンティティ型 ( EntityDataSource コントロールが実行するように設計されているタスク) を読み取り、挿入、更新、または削除するだけでなく、より簡単に実行できます。 たとえば、エンティティが更新されるたびにログ記録を実行したり、エンティティが削除されるたびにデータをアーカイブしたり、外部キー値を持つ行を挿入するときに必要に応じて関連データを自動的にチェックして更新したりすることができます。

ビジネス ロジックとリポジトリ クラス

ObjectDataSource コントロールは、作成したクラスを呼び出すことによって機能します。 このクラスには、データを取得および更新するメソッドが含まれており、それらのメソッドの名前をマークアップの ObjectDataSource コントロールに指定します。 レンダリングまたはポストバックの処理中に、 ObjectDataSource は指定したメソッドを呼び出します。

基本的な CRUD 操作に加えて、 ObjectDataSource コントロールで使用するために作成するクラスは、 ObjectDataSource がデータを読み取ったり更新したりするときにビジネス ロジックを実行する必要がある場合があります。 たとえば、部署を更新するときに、1 人のユーザーが複数の部署の管理者になることができないため、同じ管理者が他の部署にないことを検証する必要がある場合があります。

ObjectDataSourceなど、一部のドキュメントでは、ビジネス ロジックとデータ アクセス ロジックの両方を含むビジネス オブジェクトと呼ばれるクラスが呼び出されます。 このチュートリアルでは、ビジネス ロジックとデータ アクセス ロジック用に個別のクラスを作成します。 データ アクセス ロジックをカプセル化するクラスは 、リポジトリと呼ばれます。 ビジネス ロジック クラスにはビジネス ロジック メソッドとデータ アクセス メソッドの両方が含まれますが、データ アクセス メソッドはリポジトリを呼び出してデータ アクセス タスクを実行します。

また、BLL と DAL の間に抽象化レイヤーを作成し、BLL の自動単体テストを容易にします。 この抽象化レイヤーは、インターフェイスを作成し、ビジネス ロジック クラスでリポジトリをインスタンス化するときにインターフェイスを使用して実装されます。 これにより、リポジトリ インターフェイスを実装する任意のオブジェクトへの参照をビジネス ロジック クラスに提供できます。 通常の操作では、Entity Framework で動作するリポジトリ オブジェクトを指定します。 テスト用に、コレクションとして定義されたクラス変数など、簡単に操作できる方法で格納されたデータを操作するリポジトリ オブジェクトを提供します。

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

Image05

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

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

[部署] ページの外観を示すスクリーンショット。

Image02

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

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

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

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

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

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

Image80

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

Image81

[ テーブルと列 ] ダイアログ ボックスで、主キー テーブルとフィールドを Person および PersonIDに設定し、外部キー テーブルとフィールドを Department および Administratorに設定します。 (これを行うと、リレーションシップ名が FK_Department_Department から FK_Department_Person に変更されます)。

Image82

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

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

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

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

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

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

Image06

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

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

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

データ モデルの更新

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

Image07

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

Image08

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

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

Image13

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

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

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

Image14

SchoolModel (SchoolModel.Store ではない) で、vInstructorName を右クリックし、[プロパティ] を選択します。 [プロパティ] ウィンドウで、Name プロパティを "InstructorName" に変更し、エンティティ セット名プロパティを "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);
        }

    }
}

このコードは、GetDepartments エンティティ セット内のすべてのエンティティを返す 1 つのDepartments メソッドを提供します。 返されるすべての行の Person ナビゲーション プロパティにアクセスすることがわかっているため、 Include メソッドを使用して、そのプロパティの一括読み込みを指定します。 クラスは、オブジェクトが破棄されたときにデータベース接続が解放されるように、 IDisposable インターフェイスも実装します。

一般的な方法は、エンティティ型ごとにリポジトリ クラスを作成することです。 このチュートリアルでは、複数のエンティティ型に対して 1 つのリポジトリ クラスを使用します。 リポジトリ パターンの詳細については、 Entity Framework チームのブログJulie Lerman のブログの投稿を参照してください。

GetDepartments メソッドは、リポジトリ オブジェクト自体が破棄された後でも、返されたコレクションを確実に使用できるようにするために、IEnumerable オブジェクトではなく、IQueryable オブジェクトを返します。 IQueryable オブジェクトは、アクセスされるたびにデータベース アクセスを引き起こす可能性がありますが、データバインド コントロールがデータのレンダリングを試みるまでにリポジトリ オブジェクトが破棄される可能性があります。 IList オブジェクトではなく、IEnumerable オブジェクトなど、別のコレクション型を返す場合があります。 ただし、 IEnumerable オブジェクトを返すと、 foreach ループや LINQ クエリなどの一般的な読み取り専用リスト処理タスクを実行できますが、コレクション内の項目を追加または削除することはできません。これは、このような変更がデータベースに保持されることを意味する可能性があります。

Site.Master マスター ページを使用するDepartments.aspx ページを作成し、Contentという名前のContent2 コントロールに次のマークアップを追加します。

<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 コントロールを使用するため、自動データの書式設定と検証機能を利用できます。 これらを機能させるには、EnableDynamicData イベント ハンドラーで Page_Init メソッドを呼び出す必要があります。 (DynamicControl コントロールは、ナビゲーション プロパティでは機能しないため、 Administrator フィールドでは使用されません)。

入れ子になったVertical-Align="Top" コントロールを持つ列をグリッドに追加すると、後でGridView属性が重要になります。

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; }

    }
}

このコードは、メタデータをデータ モデルに追加します。 Budget エンティティのDepartment プロパティは、データ型はDecimalですが、実際には通貨を表し、値は 0 から $1,000,000.00 の間である必要があることを指定します。 また、 StartDate プロパティを mm/dd/yyyy 形式の日付として書式設定することも指定します。

Departments.aspx ページを実行します。

[部門] ページの実行時を示すスクリーンショット。

[予算] 列または [開始日] 列のDepartments.aspx ページ マークアップで書式指定文字列を指定しなかったにもかかわらず、DynamicField指定したメタデータを使用して、 コントロールによって既定の通貨と日付の書式設定が適用されていることに注意してください。

挿入と削除の機能の追加

SchoolRepository.cs開き、Insert メソッドと Delete メソッドを作成するために次のコードを追加します。 このコードには、GenerateDepartmentID メソッドで使用できる次のレコード キー値を計算する Insert という名前のメソッドも含まれています。 これは、 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 ページを作成し、Contentという名前のContent2 コントロールに次のマークアップを追加します。

<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値を取得し、Administrator エンティティのDepartment外部キー プロパティを更新します。

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

Image04

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

Image03

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

Image12

次に、ブラウザーを閉じて 、Departments.aspx ページを開きます。 Departments.aspx ページに削除機能を追加するには、DeleteMethod コントロールにObjectDataSource属性を追加し、DataKeyNames属性をGridView コントロールに追加します。 これらのコントロールの開始タグは、次の例のようになります。

<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 コントロールによって、Department メソッドに渡す 2 つのUpdateDepartment エンティティが作成されます。 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">

EditItemTemplate列のAdministrator コントロールを、その列のGridView コントロールの直後のItemTemplate コントロールに追加します。

<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 コントロールは、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 イベントのハンドラーは、参照を使用してユーザーが入力した値を取得し、Administrator エンティティのDepartment プロパティに適用します。

DepartmentsAdd.aspx ページを使用して新しい部署を追加し、Departments.aspx ページを実行し、追加した行の [編集] をクリックします。

データベース内のデータが無効なため、追加しなかった行 (つまり、データベースに既に存在していた行) を編集することはできません。データベースで作成された行の管理者は学生です。 そのうちの 1 つを編集しようとすると、次のようなエラーを報告するエラー ページが表示されます。 'InstructorsDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

Image10

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

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

[部署] ページのスクリーンショットを示します。

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