ASP.NET 4 Web アプリケーションでの Entity Framework 4.0 でのコンカレンシーの処理

作成者: Tom Dykstra

このチュートリアル シリーズは、Entity Framework 4.0 チュートリアル シリーズを使用してはじめにによって作成された Contoso University Web アプリケーションに基づいています。 以前のチュートリアルを完了していない場合は、このチュートリアルの開始点として、作成 したアプリケーションをダウンロード できます。 完全なチュートリアル シリーズによって作成された アプリケーションをダウンロード することもできます。 チュートリアルについて質問がある場合は、 ASP.NET Entity Framework フォーラムに投稿できます。

前のチュートリアルでは、コントロールと Entity Framework を使用してデータの並べ替えとフィルター処理を ObjectDataSource 行う方法について説明しました。 このチュートリアルでは、Entity Framework を使用する ASP.NET Web アプリケーションでコンカレンシーを処理するためのオプションについて説明します。 講師のオフィスの割り当ての更新専用の新しい Web ページを作成します。 コンカレンシーの問題は、そのページと、前に作成した [部門] ページで処理します。

Image06

Image01

コンカレンシーの競合

コンカレンシーの競合は、あるユーザーがレコードを編集し、別のユーザーが同じレコードを編集してから、最初のユーザーの変更がデータベースに書き込まれる前に発生します。 このような競合を検出するように Entity Framework を設定しない場合は、データベースを最後に更新したユーザーが他のユーザーの変更を上書きします。 多くのアプリケーションでは、このリスクは許容され、コンカレンシーの競合の可能性を処理するようにアプリケーションを構成する必要はありません。 (ユーザーが少ない場合、または更新プログラムが少ない場合、または一部の変更が上書きされた場合に重要でない場合は、コンカレンシーのプログラミングコストがメリットを上回る可能性があります)。コンカレンシーの競合について心配する必要がない場合は、このチュートリアルをスキップできます。シリーズの残りの 2 つのチュートリアルは、このチュートリアルで構築したものには依存しません。

ペシミスティック コンカレンシー (ロック)

コンカレンシーで偶発的にデータが失われる事態をアプリケーションで回避する必要があれば、その方法としてデータベース ロックがあります。 これは ペシミスティック コンカレンシーと呼ばれます。 たとえば、データベースから行を読む前に、読み取り専用か更新アクセスでロックを要求します。 更新アクセスで行をロックすると、他のユーザーはその行を読み取り専用または更新アクセスでロックできなくなります。変更中のデータのコピーが与えられるためです。 読み取り専用で行をロックすると、他のユーザーはその行を読み取り専用でロックできますが、更新アクセスではロックできません。

ロックの管理には、いくつかの欠点があります。 プログラムが複雑になります。 大量のデータベース管理リソースが必要であり、アプリケーションのユーザー数が増えるとパフォーマンスの問題が発生する可能性があります (つまり、適切にスケーリングされません)。 そのような理由から、一部のデータベース管理システムはペシミスティック コンカレンシーに対応していません。 Entity Framework には組み込みのサポートは提供されておらず、このチュートリアルでは実装方法については説明しません。

オプティミスティック コンカレンシー

ペシミスティック コンカレンシーの代わりに、 オプティミスティック コンカレンシーがあります。 オプティミスティック コンカレンシーでは、コンカレンシーの競合の発生を許し、発生したら適切に対処します。 たとえば、John は Department.aspx ページを実行し、履歴部門の [編集] リンクをクリックし、 予算 額を $1,000,000.00 から $125,000.00 に減らします。 (John は競合する部署を管理し、自分の部署の資金を解放したいと考えています。

Image07

John が [更新] をクリックする前に、Jane は同じページを実行し、履歴部門の [編集] リンクをクリックし、[開始日] フィールドを 2011 年 1 月 10 から 1999 年 1 月 1 日に変更します。 (ジェーンは歴史部門を管理し、より多くの年功序列を与えたいと考えています。

Image08

John は最初に [更新 ] をクリックし、[ 更新] をクリックします。 Jane のブラウザーに 予算 金額が $1,000,000.00 として一覧表示されるようになりましたが、John によって金額が $125,000.00 に変更されたため、これは正しくありません。

このシナリオで実行できるアクションには、次のようなものがあります。

  • ユーザーが変更したプロパティを追跡記録し、それに該当する列だけをデータベースで更新できます。 例のシナリオでは、2 人のユーザーが異なるプロパティを更新したため、データは失われません。 次回誰かが履歴部門を閲覧すると、1999 年 1 月 1 日と $125,000.00 が表示されます。

    これは Entity Framework の既定の動作であり、データ損失につながる可能性のある競合の数を大幅に減らすことができます。 ただし、エンティティの同じプロパティに対して競合する変更が行われた場合、この動作ではデータの損失を回避できません。 さらに、この動作は常に可能なわけではありません。ストアド プロシージャをエンティティ型にマップすると、エンティティに対する変更がデータベースで行われると、すべてのエンティティのプロパティが更新されます。

  • Jane の変更で John の変更を上書きできます。 Jane が [更新] をクリックすると、 予算 額は $1,000,000.00 に戻ります。 これは Client Wins (クライアント側に合わせる) シナリオまたは Last in Wins (最終書き込み者優先) シナリオと呼ばれています。 (クライアントの値は、データ ストア内の値よりも優先されます)。

  • データベースで Jane の変更が更新されないようにすることができます。 通常は、エラー メッセージを表示し、データの現在の状態を表示し、変更を行いたい場合は再入力できるようにします。 入力を保存し、再入力しなくても再適用する機会を与えることで、プロセスをさらに自動化できます。 これは Store Wins (ストア側に合わせる) シナリオと呼ばれています。 (クライアントが送信した値よりデータストアの値が優先されます。)

コンカレンシーの競合の検出

Entity Framework では、Entity Framework によってスローされる例外を処理 OptimisticConcurrencyException することで、競合を解決できます。 このような例外がスローされるタイミングを認識する目的で、Entity Framework は競合を検出できなければなりません。 そのため、データベースとデータ モデルを適宜構成する必要があります。 競合検出を有効にするためのオプションには次のようなものがあります。

  • データベースに、行がいつ変更されたかを判断するために使用できるテーブル列を含めます。 その後、その列を SQL Update またはDeleteコマンドの 句にWhere含むように Entity Framework を構成できます。

    それがテーブル内の列OfficeAssignmentTimestamp目的です。

    Image09

    列の Timestamp データ型は とも呼ばれます Timestamp。 ただし、列には実際には日付または時刻の値は含まれません。 代わりに、値は、行が更新されるたびにインクリメントされる連続した数値です。 Updateまたは Delete コマンドでは、 句にWhereTimestampの値が含まれます。 更新中の行が別のユーザーによって変更された場合、 の Timestamp 値は元の値とは異なるため Where 、 句は更新する行を返しません。 Entity Framework は、現在 Update または Delete コマンドによって更新された行がないことを検出すると (つまり、影響を受ける行の数が 0 の場合)、コンカレンシーの競合として解釈されます。

  • および コマンドの 句UpdateDeleteにテーブル内のすべての列の元の値を含むように Entity Framework をWhere構成します。

    最初のオプションと同様に、行が最初に読み取られた後に行内の何かが変更された場合、 Where 句は更新する行を返しません。これは、Entity Framework がコンカレンシーの競合として解釈します。 この方法は、フィールドを使用する Timestamp のと同じくらい効果的ですが、非効率的な場合があります。 列が多いデータベース テーブルの場合、非常に大きな Where 句が発生する可能性があり、Web アプリケーションでは大量の状態を維持する必要があります。 大量の状態を維持すると、サーバー リソース (セッション状態など) が必要になるか、Web ページ自体 (ビュー ステートなど) に含める必要があるため、アプリケーションのパフォーマンスに影響を与える可能性があります。

このチュートリアルでは、追跡プロパティ (エンティティ) を持たないエンティティと、追跡プロパティ ( Department エンティティ) を持つエンティティのオプティミスティック コンカレンシーの競合に対するエラー処理を OfficeAssignment 追加します。

追跡プロパティを使用しないオプティミスティック コンカレンシーの処理

追跡 (Timestamp) プロパティを持たないエンティティにDepartmentオプティミスティック コンカレンシーを実装するには、次のタスクを実行します。

  • データ モデルを変更して、エンティティのコンカレンシー追跡を Department 有効にします。
  • クラスで SchoolRepository 、 メソッドでコンカレンシー例外を SaveChanges 処理します。
  • [ Departments.aspx ] ページで、試行された変更が失敗したことを警告するメッセージをユーザーに表示して、コンカレンシー例外を処理します。 その後、ユーザーは現在の値を確認し、必要な場合は変更を再試行できます。

データ モデルでのコンカレンシー追跡の有効化

Visual Studio で、このシリーズの前のチュートリアルで使用していた Contoso University Web アプリケーションを開きます。

SchoolModel.edmx を開き、データ モデル デザイナーでエンティティの Department プロパティをName右クリックし、[プロパティ] をクリックします。 [ プロパティ ] ウィンドウで、 プロパティを ConcurrencyModeFixed変更します。

Image16

他の主キー以外のスカラー プロパティ (Budget、、StartDateおよび ) に対して同じ操作を行Administratorいます。(ナビゲーション プロパティではこれを行うことはできません)。これは、Entity Framework が データベース内のエンティティを更新Departmentする または Delete SQL コマンドを生成Updateするたびに、これらの列 (元の値を含む) を 句にWhere含める必要があることを指定します。 または Delete コマンドの実行時にUpdate行が見つからない場合、Entity Framework はオプティミスティック コンカレンシー例外をスローします。

データ モデルを保存して閉じます。

DAL でのコンカレンシー例外の処理

SchoolRepository.cs を開き、名前空間に対して次usingのステートメントをSystem.Data追加します。

using System.Data;

オプティミスティック コンカレンシー例外を処理する次の新しい SaveChanges メソッドを追加します。

public void SaveChanges()
{
    try
    {
        context.SaveChanges();
    }
    catch (OptimisticConcurrencyException ocex)
    {
        context.Refresh(RefreshMode.StoreWins, ocex.StateEntries[0].Entity);
        throw ocex;
    }
}

このメソッドが呼び出されたときにコンカレンシー エラーが発生した場合、メモリ内のエンティティのプロパティ値は、データベース内の現在の値に置き換えられます。 コンカレンシー例外は、Web ページで処理できるように再スローされます。

DeleteDepartmentメソッドと UpdateDepartment メソッドで、 の既存の呼び出しを context.SaveChanges() の呼び出しにSaveChanges()置き換えて、新しいメソッドを呼び出します。

プレゼンテーション 層でのコンカレンシー例外の処理

Departments.aspx を開き、コントロールにOnDeleted="DepartmentsObjectDataSource_Deleted"属性をDepartmentsObjectDataSource追加します。 コントロールの開始タグは、次の例のようになります。

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.BLL.SchoolBL" DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartmentsByName" DeleteMethod="DeleteDepartment" UpdateMethod="UpdateDepartment"
        ConflictDetection="CompareAllValues" OldValuesParameterFormatString="orig{0}" 
        OnUpdated="DepartmentsObjectDataSource_Updated" SortParameterName="sortExpression" 
        OnDeleted="DepartmentsObjectDataSource_Deleted" >

コントロールで DepartmentsGridView 、次の例に示すように、 属性内 DataKeyNames のすべてのテーブル列を指定します。 これにより、非常に大きなビューステートフィールドが作成されることに注意してください。これは、通常、コンカレンシーの競合を追跡するための推奨される方法が追跡フィールドを使用する理由の1つです。

<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" 
        DataKeyNames="DepartmentID,Name,Budget,StartDate,Administrator" 
        OnRowUpdating="DepartmentsGridView_RowUpdating"
        OnRowDataBound="DepartmentsGridView_RowDataBound"
        AllowSorting="True" >

Departments.aspx.cs を開き、 名前空間に次usingのステートメントをSystem.Data追加します。

using System.Data;

次の新しいメソッドを追加します。このメソッドは、コンカレンシー例外を処理するためにデータ ソース コントロール UpdatedDeleted イベント ハンドラーから呼び出します。

private void CheckForOptimisticConcurrencyException(ObjectDataSourceStatusEventArgs e, string function)
{
    if (e.Exception.InnerException is OptimisticConcurrencyException)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = 
            "The record you attempted to edit or delete was modified by another " +
            "user after you got the original value. The edit or delete operation was canceled " +
            "and the other user's values have been displayed so you can " +
            "determine whether you still want to edit or delete this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

このコードは例外の種類をチェックし、同時実行例外の場合は、コントロールにメッセージValidationSummaryCustomValidator表示するコントロールを動的に作成します。

前に追加した Updated イベント ハンドラーから新しいメソッドを呼び出します。 さらに、同じメソッドを呼び出す新しい Deleted イベント ハンドラーを作成します (ただし、他には何もしません)。

protected void DepartmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        CheckForOptimisticConcurrencyException(e, "update");
        // ...
    }
}

protected void DepartmentsObjectDataSource_Deleted(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        CheckForOptimisticConcurrencyException(e, "delete");
    }
}

[部門] ページでのオプティミスティック コンカレンシーのテスト

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

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

行の [編集] をクリックし、[ 予算 ] 列の値を変更します。 (既存 School のデータベース レコードには無効なデータが含まれているため、このチュートリアル用に作成したレコードのみを編集できることに注意してください。経済部の記録は、実験しても安全です。

Image18

新しいブラウザー ウィンドウを開き、ページをもう一度実行します (最初のブラウザー ウィンドウのアドレス ボックスから 2 番目のブラウザー ウィンドウに URL をコピーします)。

入力の準備ができている新しいブラウザー ウィンドウを示すスクリーンショット。

前に編集した行と同じ行の [ 編集] をクリックし、[ 予算 ] の値を別のものに変更します。

Image19

2 番目のブラウザー ウィンドウで、[ 更新] をクリックします。 予算額は、この新しい値に正常に変更されました。

Image20

最初のブラウザー ウィンドウで、[ 更新] をクリックします。 更新は失敗します。 予算額は、2 番目のブラウザー ウィンドウで設定した値を使用して再表示され、エラー メッセージが表示されます。

Image21

追跡プロパティを使用したオプティミスティック コンカレンシーの処理

追跡プロパティを持つエンティティのオプティミスティック コンカレンシーを処理するには、次のタスクを完了します。

  • エンティティを管理 OfficeAssignment するストアド プロシージャをデータ モデルに追加します。 (追跡プロパティとストアド プロシージャを一緒に使用する必要はありません。ここでは、説明のためにグループ化されています)。
  • DAL のオプティミスティック 同時実行例外を処理するコードを含む、エンティティの OfficeAssignment DAL および BLL に CRUD メソッドを追加します。
  • office-assignments Web ページを作成します。
  • 新しい Web ページでオプティミスティック コンカレンシーをテストします。

データ モデルへの OfficeAssignment ストアド プロシージャの追加

モデル デザイナーで SchoolModel.edmx ファイルを開き、デザイン画面を右クリックして、[ データベースからモデルを更新] をクリックします。 [データベース オブジェクトの選択] ダイアログ ボックスの [追加] タブで、[ストアド プロシージャ] を展開し、3 つのOfficeAssignmentストアド プロシージャを選択して (次のスクリーンショットを参照)、[完了] をクリックします。 (これらのストアド プロシージャは、スクリプトを使用してダウンロードまたは作成したときにデータベースに既に存在していました)。

Image02

エンティティを OfficeAssignment 右クリックし、[ ストアド プロシージャ マッピング] を選択します。

Image03

InsertUpdate、Delete 関数を、対応するストアド プロシージャを使用するように設定します。 関数のパラメーターに OrigTimestamp 対して Updateプロパティ を に Timestamp 設定し、 [ 元の値を使用 ] オプションを選択します。

Image04

Entity Framework がストアド プロシージャをUpdateOfficeAssignment呼び出すと、 パラメーターに列の元の値がTimestampOrigTimestamp渡されます。 ストアド プロシージャは、 句でこのパラメーターを Where 使用します。

ALTER PROCEDURE [dbo].[UpdateOfficeAssignment]
    @InstructorID int,
    @Location nvarchar(50),
    @OrigTimestamp timestamp
    AS
    UPDATE OfficeAssignment SET Location=@Location 
    WHERE InstructorID=@InstructorID AND [Timestamp]=@OrigTimestamp;
    IF @@ROWCOUNT > 0
    BEGIN
        SELECT [Timestamp] FROM OfficeAssignment 
            WHERE InstructorID=@InstructorID;
    END

ストアド プロシージャでは、更新後に列の Timestamp 新しい値も選択されるため、Entity Framework はメモリ内のエンティティを対応するデータベース行と同期させることができます OfficeAssignment

(Office の割り当てを削除するためのストアド プロシージャにはパラメーターが含 OrigTimestamp まれていないことに注意してください。このため、Entity Framework では、削除する前にエンティティが変更されていないことを確認できません)。

データ モデルを保存して閉じます。

DAL への OfficeAssignment メソッドの追加

ISchoolRepository.cs を開き、エンティティ セットに次の CRUD メソッドをOfficeAssignment追加します。

IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression);
void InsertOfficeAssignment(OfficeAssignment OfficeAssignment);
void DeleteOfficeAssignment(OfficeAssignment OfficeAssignment);
void UpdateOfficeAssignment(OfficeAssignment OfficeAssignment, OfficeAssignment origOfficeAssignment);

SchoolRepository.cs に次の新しいメソッドを追加します。 メソッドではUpdateOfficeAssignment、 ではなくcontext.SaveChangesローカル SaveChanges メソッドを呼び出します。

public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    return new ObjectQuery<OfficeAssignment>("SELECT VALUE o FROM OfficeAssignments AS o", context).Include("Person").OrderBy("it." + sortExpression).ToList();
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    context.OfficeAssignments.AddObject(officeAssignment);
    context.SaveChanges();
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    context.OfficeAssignments.Attach(officeAssignment);
    context.OfficeAssignments.DeleteObject(officeAssignment);
    context.SaveChanges();
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    context.OfficeAssignments.Attach(origOfficeAssignment);
    context.ApplyCurrentValues("OfficeAssignments", officeAssignment);
    SaveChanges();
}

テスト プロジェクトで MockSchoolRepository.cs を開き、次 OfficeAssignment のコレクションと CRUD メソッドを追加します。 (モック リポジトリはリポジトリ インターフェイスを実装する必要があります。そうしないと、ソリューションはコンパイルされません)。

List<OfficeAssignment> officeAssignments = new List<OfficeAssignment>();
        
public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    return officeAssignments;
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    officeAssignments.Add(officeAssignment);
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    officeAssignments.Remove(officeAssignment);
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    officeAssignments.Remove(origOfficeAssignment);
    officeAssignments.Add(officeAssignment);
}

BLL への OfficeAssignment メソッドの追加

メイン プロジェクトで SchoolBL.cs を開き、エンティティ セットに対して次の CRUD メソッドをOfficeAssignment追加します。

public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    if (string.IsNullOrEmpty(sortExpression)) sortExpression = "Person.LastName";
    return schoolRepository.GetOfficeAssignments(sortExpression);
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    try
    {
        schoolRepository.InsertOfficeAssignment(officeAssignment);
    }
    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 DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    try
    {
        schoolRepository.DeleteOfficeAssignment(officeAssignment);
    }
    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 UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    try
    {
        schoolRepository.UpdateOfficeAssignment(officeAssignment, origOfficeAssignment);
    }
    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;
    }
}

OfficeAssignments Web ページの作成

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

<h2>Office Assignments</h2>
    <asp:ObjectDataSource ID="OfficeAssignmentsObjectDataSource" runat="server" TypeName="ContosoUniversity.BLL.SchoolBL"
        DataObjectTypeName="ContosoUniversity.DAL.OfficeAssignment" SelectMethod="GetOfficeAssignments"
        DeleteMethod="DeleteOfficeAssignment" UpdateMethod="UpdateOfficeAssignment" ConflictDetection="CompareAllValues"
        OldValuesParameterFormatString="orig{0}"
        SortParameterName="sortExpression"  OnUpdated="OfficeAssignmentsObjectDataSource_Updated">
    </asp:ObjectDataSource>
    <asp:ValidationSummary ID="OfficeAssignmentsValidationSummary" runat="server" ShowSummary="true"
        DisplayMode="BulletList" Style="color: Red; width: 40em;" />
    <asp:GridView ID="OfficeAssignmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="OfficeAssignmentsObjectDataSource" DataKeyNames="InstructorID,Timestamp"
        AllowSorting="True">
        <Columns>
            <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" ItemStyle-VerticalAlign="Top">
                <ItemStyle VerticalAlign="Top"></ItemStyle>
            </asp:CommandField>
            <asp:TemplateField HeaderText="Instructor" SortExpression="Person.LastName">
                <ItemTemplate>
                    <asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
                    <asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:DynamicField DataField="Location" HeaderText="Location" SortExpression="Location"/>
        </Columns>
        <SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
    </asp:GridView>

属性ではDataKeyNames、マークアップによって プロパティとレコード キー (InstructorID) が指定Timestampされていることに注意してください。 属性にプロパティを DataKeyNames 指定すると、コントロールはコントロール状態 (ビューステートに似ています) に保存され、ポストバック処理中に元の値を使用できるようになります。

値を保存Timestampしなかった場合、Entity Framework は SQL Update コマンドの 句に対Whereして値を持っていません。 その結果、更新するものは何も見つかりませんでした。 その結果、エンティティフレームワークは、エンティティが更新されるたびにオプティミスティック コンカレンシー例外を OfficeAssignment スローします。

OfficeAssignments.aspx.cs を開き、データ アクセス層に次usingのステートメントを追加します。

using ContosoUniversity.DAL;

動的データ機能を有効にする次 Page_Init のメソッドを追加します。 また、コンカレンシー エラーをObjectDataSourceチェックするために、コントロールのUpdatedイベントに次のハンドラーを追加します。

protected void Page_Init(object sender, EventArgs e)
{
    OfficeAssignmentsGridView.EnableDynamicData(typeof(OfficeAssignment));
}

protected void OfficeAssignmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = "The record you attempted to " +
            "update has been modified by another user since you last visited this page. " +
            "Your update was canceled to allow you to review the other user's " +
            "changes and determine if you still want to update this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

OfficeAssignments ページでのオプティミスティック コンカレンシーのテスト

OfficeAssignments.aspx ページを実行します

[Office の割り当て] ページを示すスクリーンショット。

行の [ 編集] を クリックし、[ 場所 ] 列の値を変更します。

Image11

新しいブラウザー ウィンドウを開き、ページをもう一度実行します (最初のブラウザー ウィンドウから 2 番目のブラウザー ウィンドウに URL をコピーします)。

新しいブラウザー ウィンドウを示すスクリーンショット。

前に編集した行と同じ行の [ 編集] をクリックし、[ 場所 ] の値を別のものに変更します。

Image12

2 番目のブラウザー ウィンドウで、[ 更新] をクリックします。

Image13

最初のブラウザー ウィンドウに切り替えて、[ 更新] をクリックします。

Image15

エラー メッセージが表示され、[ 場所 ] の値が更新され、2 番目のブラウザー ウィンドウでに変更した値が表示されます。

EntityDataSource コントロールを使用したコンカレンシーの処理

コントロールには EntityDataSource 、データ モデルのコンカレンシー設定を認識し、それに応じて更新操作と削除操作を処理する組み込みロジックが含まれています。 ただし、すべての例外と同様に、ユーザー フレンドリなエラー メッセージを提供するには、例外を自分で処理 OptimisticConcurrencyException する必要があります。

次に、 Courses.aspx ページ (コントロールを EntityDataSource 使用) を構成して、更新操作と削除操作を許可し、コンカレンシーの競合が発生した場合にエラー メッセージを表示します。 エンティティには Course コンカレンシー追跡列がないため、エンティティで行ったのと同じメソッドを Department 使用します。キー以外のすべてのプロパティの値を追跡します。

SchoolModel.edmx ファイルを開きます。 エンティティ (Title、、) DepartmentIDのキー以外のプロパティのCourse場合は、Creditsコンカレンシー モード プロパティを にFixed設定します。 次に、データ モデルを保存して閉じます。

Courses.aspx ページを開き、次の変更を行います。

  • コントロールでCoursesEntityDataSource、 属性と 属性をEnableDelete="true"追加EnableUpdate="true"します。 そのコントロールの開始タグは、次の例のようになります。

    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" 
            AutoGenerateWhereClause="True" EntitySetName="Courses"
            EnableUpdate="true" EnableDelete="true">
    
  • コントロールで CoursesGridView 、属性値を DataKeyNames"CourseID,Title,Credits,DepartmentID"変更します。 次に、[CommandField編集] ボタンと [削除] Columns ボタン (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />) を表示する要素を 要素に追加します。 コントロールは GridView 次の例のようになります。

    <asp:GridView ID="CoursesGridView" runat="server" AutoGenerateColumns="False" 
            DataKeyNames="CourseID,Title,Credits,DepartmentID"
            DataSourceID="CoursesEntityDataSource" >
            <Columns>
                <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
                <asp:BoundField DataField="CourseID" HeaderText="CourseID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" />
            </Columns>
        </asp:GridView>
    

ページを実行し、[部門] ページで前と同じように競合状態を作成します。 2 つのブラウザー ウィンドウでページを実行し、各ウィンドウの同じ行にある [編集] をクリックして、それぞれ異なる変更を行います。 1 つのウィンドウで [ 更新 ] をクリックし、もう一方のウィンドウで [ 更新 ] をクリックします。 2 回目の 更新 をクリックすると、ハンドルされないコンカレンシー例外の結果として発生するエラー ページが表示されます。

Image22

このエラーは、コントロールの処理方法とよく似た方法で処理 ObjectDataSource します。 Courses.aspx ページを開き、コントロールで CoursesEntityDataSource イベントと Updated イベントのハンドラーをDeleted指定します。 コントロールの開始タグは、次の例のようになります。

<asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
        ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false"
        AutoGenerateWhereClause="true" EntitySetName="Courses" 
        EnableUpdate="true" EnableDelete="true" 
        OnDeleted="CoursesEntityDataSource_Deleted" 
        OnUpdated="CoursesEntityDataSource_Updated">

コントロールの前に CoursesGridView 、次のコントロールを追加します ValidationSummary

<asp:ValidationSummary ID="CoursesValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

Courses.aspx.cs で、名前空間の ステートメントをusingSystem.Data追加し、コンカレンシー例外をチェックするメソッドを追加し、コントロールの ハンドラーと Deleted ハンドラーのUpdatedハンドラーをEntityDataSource追加します。 コードは次のようになります。

using System.Data;
protected void CoursesEntityDataSource_Updated(object sender, EntityDataSourceChangedEventArgs e)
{
    CheckForOptimisticConcurrencyException(e, "update");
}

protected void CoursesEntityDataSource_Deleted(object sender, EntityDataSourceChangedEventArgs e)
{
    CheckForOptimisticConcurrencyException(e, "delete");
}

private void CheckForOptimisticConcurrencyException(EntityDataSourceChangedEventArgs e, string function)
{
    if (e.Exception != null && e.Exception is OptimisticConcurrencyException)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = 
            "The record you attempted to edit or delete was modified by another " +
            "user after you got the original value. The edit or delete operation was canceled " +
            "and the other user's values have been displayed so you can " +
            "determine whether you still want to edit or delete this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

このコードとコントロールに対 ObjectDataSource して行ったことの唯一の違いは、この場合、コンカレンシー例外は Exception 、その例外のプロパティではなく、イベント引数オブジェクトの InnerException プロパティにあることです。

ページを実行し、コンカレンシーの競合をもう一度作成します。 今回は、次のエラー メッセージが表示されます。

Image23

コンカレンシーの競合処理の入門編はこれで終わりです。 次のチュートリアルでは、Entity Framework を使用する Web アプリケーションのパフォーマンスを向上させる方法に関するガイダンスを提供します。