在 ASP.NET 4 Web 應用程式中處理 Entity Framework 4.0 的並行處理

作者 :Tom Dykstra

本教學課程系列是以 Contoso University Web 應用程式為基礎,此應用程式是由使用Entity Framework 4.0教學課程系列消費者入門所建立。 如果您未完成先前的教學課程,作為本教學課程的起點,您可以下載您已建立 的應用程式 。 您也可以下載完整教學課程系列所建立 的應用程式 。 如果您有關于教學課程的問題,您可以將這些教學課程張貼到 ASP.NET Entity Framework 論壇

在上一個教學課程中,您已瞭解如何使用 ObjectDataSource 控制項和 Entity Framework 來排序和篩選資料。 本教學課程示範在使用 Entity Framework 的 ASP.NET Web 應用程式中處理並行的選項。 您將建立新的網頁,其專用於更新講師辦公室指派。 您將處理該頁面和稍早建立的 [部門] 頁面中的並行問題。

Image06

Image01

並行衝突

當某個使用者編輯記錄,而另一位使用者在第一位使用者的變更寫入資料庫之前編輯相同的記錄時,就會發生並行衝突。 如果您未設定 Entity Framework 來偵測這類衝突,則每當更新資料庫的人員最後覆寫其他使用者的變更。 在許多應用程式中,此風險是可接受的,而且您不需要設定應用程式來處理可能的並行衝突。 (如果有少數使用者或少數更新,或如果某些變更遭到覆寫,則並行程式設計的成本可能超過權益。) 如果您不需要擔心並行衝突,您可以略過本教學課程;本系列中其餘的兩個教學課程不取決於您在此系列中建置的任何專案。

封閉式並行 (鎖定)

若您的應用程式確實需要防止在並行案例下發生的意外資料遺失,其中一個方法便是使用資料庫鎖定。 這稱為 封閉式並行存取。 例如,在您從資料庫讀取一個資料列之前,您會要求唯讀鎖定或更新存取鎖定。 若您鎖定了一個資料列以進行更新存取,其他使用者便無法為了唯讀或更新存取而鎖定該資料列,因為他們會取得一個正在進行變更之資料的複本。 若您鎖定資料列以進行唯讀存取,其他使用者也可以為了唯讀存取將其鎖定,但無法進行更新。

管理鎖定有一些缺點。 其程式可能相當複雜。 它需要大量的資料庫管理資源,而且可能會造成效能問題,因為應用程式的使用者數目增加 (,也就是說,它無法妥善調整) 。 基於這些理由,不是所有的資料庫管理系統都支援封閉式並行存取。 Entity Framework 不提供其內建支援,本教學課程不會示範如何實作它。

開放式並行存取

封閉式平行存取的替代方式是 開放式平行存取。 開放式並行存取表示允許並行衝突發生,然後在衝突發生時適當的做出反應。 例如,John 會執行 Department.aspx 頁面,按一下 [歷程記錄] 部門的 [編輯 ] 連結,並將 預算 金額從 $1,000,000.00 減少為 $125,000.00。 (John 管理競爭部門,並想要為自己的部門釋出金錢。)

Image07

在 John 按一下 [更新]之前,Jane 會執行相同的頁面,按一下 [歷程記錄] 部門的 [編輯 ] 連結,然後將 [ 開始日期] 欄位從 2011/1/1 變更為 1/1/1999。 (Jane 管理歷程記錄部門,並想要給予它更資深。)

Image08

John 先按一下 [更新 ],然後按一下 [Jane ] [更新]。 Jane 的瀏覽器現在會將 預算 金額列為 $1,000,000.00,但不正確,因為 John 已將金額變更為 $125,000.00。

您可以在此案例中採取的一些動作包括:

  • 您可以追蹤使用者修改的屬性,然後僅在資料庫中更新相對應的資料行。 在範例案例中,將不會發生資料遺失,因為兩名使用者更新的屬性不同。 下次有人流覽歷程記錄部門時,他們會看到 1/1/1999 和 $125,000.00。

    這是 Entity Framework 中的預設行為,可大幅減少可能導致資料遺失的衝突數目。 不過,如果對實體的相同屬性進行競爭變更,此行為不會避免資料遺失。 此外,此行為不一定可行;當您將預存程式對應至實體類型時,當資料庫中對實體所做的任何變更時,所有實體的屬性都會更新。

  • 您可以讓 Jane 的變更覆寫 John 的變更。 在 Jane 按一下 [更新] 之後, 預算 金額會回復為 $1,000,000.00。 這稱之為「用戶端獲勝 (Client Wins)」或「最後寫入為準 (Last in Wins)」案例。 (用戶端的值優先于資料存放區中的內容。)

  • 您可以防止 Jane 在資料庫中更新。 一般而言,您會顯示錯誤訊息、顯示其目前的資料狀態,並在她仍想要進行變更時,允許她重新輸入變更。 您可以藉由儲存其輸入,並讓她有機會重新套用,而不需重新輸入,進一步將程式自動化。 這稱為「存放區獲勝 (Store Wins)」案例。 (資料存放區的值會優先於用戶端所提交的值。)

偵測並行衝突

在 Entity Framework 中,您可以處理 OptimisticConcurrencyException Entity Framework 擲回的例外狀況來解決衝突。 若要得知何時應擲回這些例外狀況,Entity Framework 必須能夠偵測衝突。 因此,您必須適當的設定資料庫及資料模型。 一部分啟用衝突偵測的選項包括下列選項:

  • 在資料庫中,包含可用來判斷資料列何時變更的資料表資料行。 然後,您可以將 Entity Framework 設定為將該資料行包含在 SQL UpdateDelete 命令的 子句中 Where

    這是資料表中 OfficeAssignment 資料行的目的 Timestamp

    Image09

    資料行的 Timestamp 資料類型也稱為 Timestamp 。 不過,資料行實際上不包含日期或時間值。 相反地,此值是每次更新資料列時遞增的循序數位。 Update在 或 Delete 命令中 Where ,子句包含原始 Timestamp 值。 如果另一位使用者已變更要更新的資料列,中的 Timestamp 值會與原始值不同,因此 Where 子句不會傳回任何要更新的資料列。 當 Entity Framework 發現目前 UpdateDelete 命令未更新任何資料列, (也就是說,當受影響的資料列數目為零) 時,它會將它解譯為並行衝突。

  • 設定 Entity Framework,以在 和 Delete 命令的 子句 Update 中包含資料表 Where 中每個資料行的原始值。

    如同第一個選項,如果在第一次讀取資料列之後,資料列中的任何專案都已變更, Where 子句將不會傳回要更新的資料列,而 Entity Framework 會解譯為並行衝突。 這個方法與使用 Timestamp 欄位一樣有效,但效率不佳。 對於具有許多資料行的資料庫資料表,可能會導致非常大型 Where 的子句,而 Web 應用程式中可能需要您維護大量的狀態。 維護大量狀態可能會影響應用程式效能,因為它需要伺服器資源 (例如會話狀態) ,或必須包含在網頁本身 (例如檢視狀態) 。

在本教學課程中,您將針對實體 () Department 沒有追蹤屬性的實體,以及具有追蹤屬性 OfficeAssignment 的實體, (實體) 新增開放式並行衝突的錯誤處理。

處理沒有追蹤屬性的開放式平行存取

若要實作 Department 實體的開放式平行存取,該實體沒有追蹤 (Timestamp) 屬性,您將完成下列工作:

  • 變更資料模型以啟用實體的並行追蹤 Department
  • 在 類別中 SchoolRepository ,處理 方法中的 SaveChanges 並行例外狀況。
  • Departments.aspx 頁面中,顯示訊息給使用者警告,指出嘗試的變更失敗,以處理並行例外狀況。 然後,使用者可以查看目前的值,並視需要重試變更。

在資料模型中啟用並行追蹤

在 Visual Studio 中,開啟您在本系列上一個教學課程中使用的 Contoso University Web 應用程式。

開啟 SchoolModel.edmx,然後在資料模型設計工具中,以滑鼠右鍵按一下 Name 實體中的 Department 屬性,然後按一下 [ 屬性]。 在 [ 屬性] 視窗中,將 ConcurrencyMode 屬性變更為 Fixed

Image16

針對其他非主鍵純量屬性執行相同的動作, (、 和 .) (您無法對導覽屬性執行這項操作。) 這指定每當 Entity Framework 產生 UpdateDelete SQL 命令來更新 Department 資料庫中的實體時,都必須在 Where 子句中包含具有原始值的這些資料行) (。 AdministratorStartDateBudget 如果在 或 Delete 命令執行時 Update 找不到任何資料列,Entity Framework 將會擲回開放式平行存取例外狀況。

儲存並關閉資料模型。

處理 DAL 中的並行例外狀況

開啟SchoolRepository.cs,並新增命名空間的 System.Data 下列 using 語句:

using System.Data;

新增下列處理開放式平行存取例外狀況的新 SaveChanges 方法:

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

如果呼叫此方法時發生並行錯誤,記憶體中實體的屬性值會取代為資料庫中目前的值。 並行例外狀況會重新擲回,讓網頁可以處理它。

在 和 UpdateDepartment 方法中 DeleteDepartment ,將現有的呼叫取代為 的呼叫 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 資料表資料行,如下列範例所示。 請注意,這會建立非常大的檢視狀態欄位,這是使用追蹤欄位通常是追蹤並行衝突的慣用方式之一。

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

此程式碼會檢查例外狀況類型,如果它是並行例外狀況,程式碼就會動態建立 CustomValidator 控制項,進而在 ValidationSummary 控制項中顯示訊息。

從您稍早新增的事件處理常式呼叫 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

開啟新的瀏覽器視窗,然後再次執行頁面, (將 URL 從第一個瀏覽器視窗的位址方塊複製到第二個瀏覽器視窗) 。

顯示可供輸入的新瀏覽器視窗的螢幕擷取畫面。

按一下您稍早編輯之相同資料列中的 [ 編輯 ],並將 [預算 ] 值變更為不同的值。

Image19

在第二個瀏覽器視窗中,按一下 [ 更新]。 預算金額已成功變更為這個新值。

Image20

在第一個瀏覽器視窗中,按一下 [ 更新]。 更新失敗。 預算金額會使用您在第二個瀏覽器視窗中設定的值重新顯示,您會看到錯誤訊息。

Image21

使用追蹤屬性處理開放式平行存取

若要處理具有追蹤屬性之實體的開放式平行存取,您將完成下列工作:

  • 將預存程式新增至資料模型來管理 OfficeAssignment 實體。 (追蹤屬性和預存程式不需要一起使用;它們只是在這裡分組以取得圖例。)
  • 將 CRUD 方法新增至 DAL 和適用于實體的 BLL OfficeAssignment ,包括用來處理 DAL 中開放式平行存取例外狀況的程式碼。
  • 建立辦公室指派網頁。
  • 在新網頁中測試開放式平行存取。

將 OfficeAssignment 預存程式新增至資料模型

在模型設計工具中開啟 SchoolModel.edmx 檔案,以滑鼠右鍵按一下設計介面,然後按一下 [從資料庫更新模型]。 在 [選擇您的資料庫物件] 對話方塊的 [新增] 索引標籤中,展開 [預存程式],然後選取三 OfficeAssignment 個預存程式 (查看下列螢幕擷取畫面) ,然後按一下 [完成]。 (當您使用 script.) 下載或建立預存程式時,這些預存程式已在資料庫中

Image02

以滑鼠右鍵按一下 OfficeAssignment 實體,然後選取 [預存程式對應]。

Image03

InsertUpdateDelete 函式設定為使用其對應的預存程式。 針對函 OrigTimestamp 式的參數 Update ,將 [屬性 ] 設定為 Timestamp ,然後選取 [使用原始值 ] 選項。

Image04

當 Entity Framework 呼叫 UpdateOfficeAssignment 預存程式時,它會在 參數中 OrigTimestamp 傳遞資料行的原始值 Timestamp 。 預存程式在其 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 記憶體中的實體與對應的資料庫資料列保持同步。

(請注意,刪除辦公室指派的預存程式沒有 OrigTimestamp 參數。因此,Entity Framework 無法在刪除實體之前確認實體未變更。)

儲存並關閉資料模型。

將 OfficeAssignment 方法新增至 DAL

開啟 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 ,您會呼叫本機 SaveChanges 方法, context.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);
}

將 OfficeAssignment 方法新增至 BLL

在主要專案中,開啟 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 網頁

建立使用 Site.Master 主 版頁面的新網頁,並將它命名為 OfficeAssignments.aspx。 將下列標記新增至名為 Content2Content 控制項:

<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 ,標記會 Timestamp 指定 屬性以及記錄索引鍵 (InstructorID) 。 在 屬性中 DataKeyNames 指定屬性會導致控制項將它們儲存在控制項狀態 (,這類似于檢視狀態) ,以便在回傳處理期間使用原始值。

如果您未儲存 Timestamp 值,Entity Framework 就不會針對 Where SQL Update 命令的 子句使用它。 因此,找不到任何可更新的專案。 因此,每次更新實體時 OfficeAssignment ,Entity Framework 都會擲回開放式平行存取例外狀況。

開啟 OfficeAssignments.aspx.cs ,並為數據存取層新增下列 using 語句:

using ContosoUniversity.DAL;

新增下列 Page_Init 方法,以啟用動態資料功能。 同時新增控制項 Updated 事件的下列處理常式 ObjectDataSource ,以檢查並行錯誤:

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

開啟新的瀏覽器視窗,然後再次執行頁面, (將 URL 從第一個瀏覽器視窗複製到第二個瀏覽器視窗) 。

顯示新瀏覽器視窗的螢幕擷取畫面。

按一下您稍早編輯之相同資料列中的 [ 編輯 ],並將 [位置] 值變更為不同的值。

Image12

在第二個瀏覽器視窗中,按一下 [ 更新]。

Image13

切換至第一個瀏覽器視窗,然後按一下 [ 更新]。

Image15

您會看到錯誤訊息,且 [位置] 值已更新,以顯示您在第二個瀏覽器視窗中將它變更為的值。

使用 EntityDataSource 控制項處理並行

控制項 EntityDataSource 包含內建邏輯,可辨識資料模型中的並行設定,並據以處理更新和刪除作業。 不過,如同所有例外狀況,您必須自行處理 OptimisticConcurrencyException 例外狀況,才能提供使用者易記的錯誤訊息。

接下來,您將設定 Courses.aspx 頁面 (,它會使用 EntityDataSource 控制項) 來允許更新和刪除作業,並在發生並行衝突時顯示錯誤訊息。 實體 Course 沒有並行追蹤資料行,因此您將使用與實體相同的 Department 方法:追蹤所有非索引鍵屬性的值。

開啟 SchoolModel.edmx 檔案。 對於實體的非索引鍵屬性 Course , (TitleCreditsDepartmentID) ,請將 並行模式 屬性設定為 Fixed 。 然後儲存並關閉資料模型。

開啟 Courses.aspx 頁面,並進行下列變更:

  • 在 控制項中 CoursesEntityDataSource ,新增 EnableUpdate="true"EnableDelete="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>
    

執行頁面並建立衝突情況,如同您在 [部門] 頁面中所做的一樣。 在兩個瀏覽器視窗中執行頁面,按一下每個視窗中相同行的 [編輯 ],並在每一個視窗中進行不同的變更。 按一下一個視窗中的 [更新 ],然後按一下另一個視窗中的 [ 更新 ]。 當您第二次按一下 [ 更新 ] 時,您會看到由未處理的並行例外狀況所產生的錯誤頁面。

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中,新增 命名空間的 System.Data 語句、新增 using 檢查並行例外狀況的方法,以及新增控制項和 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 應用程式中改善效能的指引。