Freigeben über


Behandeln von Parallelität mit Entity Framework 4.0 in einer ASP.NET 4-Webanwendung

Von Tom Dykstra

Diese Lernprogrammreihe basiert auf der Contoso University-Webanwendung, die von der Lernprogrammreihe "Getting Started with the Entity Framework 4.0 " erstellt wird. Wenn Sie die früheren Lernprogramme nicht abgeschlossen haben, können Sie als Ausgangspunkt für dieses Lernprogramm die Anwendung herunterladen , die Sie erstellt haben. Sie können auch die Anwendung herunterladen , die von der vollständigen Lernprogrammreihe erstellt wird. Wenn Sie Fragen zu den Lernprogrammen haben, können Sie sie im Forum ASP.NET Entity Framework posten.

Im vorherigen Lernprogramm haben Sie gelernt, wie Sie Daten mithilfe des ObjectDataSource Steuerelements und des Entity Framework sortieren und filtern. In diesem Lernprogramm werden Optionen für die Behandlung der Parallelität in einer ASP.NET-Webanwendung gezeigt, die das Entity Framework verwendet. Sie erstellen eine neue Webseite, die dem Aktualisieren von Kursleiter-Büroaufgaben gewidmet ist. Sie behandeln Parallelitätsprobleme auf dieser Seite und auf der Seite "Abteilungen", die Sie zuvor erstellt haben.

Bild06

Bild01

Nebenläufigkeitskonflikte

Ein Parallelitätskonflikt tritt auf, wenn ein Benutzer einen Datensatz bearbeitet und ein anderer Benutzer denselben Datensatz bearbeitet, bevor die Änderung des ersten Benutzers in die Datenbank geschrieben wird. Wenn Sie das Entity Framework nicht zum Erkennen solcher Konflikte einrichten, überschreibt jeder, der die Datenbank zuletzt aktualisiert, die Änderungen des anderen Benutzers. In vielen Anwendungen ist dieses Risiko akzeptabel, und Sie müssen die Anwendung nicht so konfigurieren, dass mögliche Parallelitätskonflikte behandelt werden. (Wenn es nur wenige Benutzer oder wenige Updates gibt oder wenn es nicht wirklich kritisch ist, wenn einige Änderungen überschrieben werden, kann die Programmierungskosten für Parallelität den Vorteil überwiegen.) Wenn Sie sich keine Gedanken über Parallelitätskonflikte machen müssen, können Sie dieses Lernprogramm überspringen. die verbleibenden beiden Lernprogramme in der Reihe hängen nicht von etwas ab, das Sie in diesem Lernprogramm erstellen.

Pessimistische Parallelität (Sperren)

Wenn Ihre Anwendung versehentliche Datenverluste in Parallelitätsszenarios verhindern muss, ist die Verwendung von Datenbanksperren eine Möglichkeit. Dies wird als pessimistische Parallelität bezeichnet. Bevor Sie zum Beispiel eine Zeile aus einer Datenbank lesen, fordern Sie eine Sperre für den schreibgeschützten Zugriff oder den Aktualisierungszugriff an. Wenn Sie eine Zeile für den Aktualisierungszugriff sperren, kann kein anderer Benutzer diese Zeile für den schreibgeschützten Zugriff oder den Aktualisierungszugriff sperren, da er eine Kopie der Daten erhalten würde, die gerade geändert werden. Wenn Sie eine Zeile für den schreibgeschützten Zugriff sperren, können andere diese Zeile ebenfalls für den schreibgeschützten Zugriff sperren, aber nicht für den Aktualisierungszugriff.

Das Verwalten von Sperren hat einige Nachteile. Es kann komplex sein, sie zu programmieren. Es erfordert erhebliche Datenbankverwaltungsressourcen, und es kann Leistungsprobleme verursachen, da die Anzahl der Benutzer einer Anwendung zunimmt (d. h., es wird nicht gut skaliert). Aus diesen Gründen unterstützen nicht alle Datenbankverwaltungssysteme die pessimistische Parallelität. Das Entity Framework bietet keine integrierte Unterstützung dafür, und in diesem Lernprogramm wird nicht gezeigt, wie Sie es implementieren.

Optimistische Parallelität

Die Alternative zur pessimistischen Parallelität ist optimistische Parallelität. Die Verwendung der optimistischen Parallelität bedeutet, Nebenläufigkeitskonflikte zu erlauben und entsprechend zu reagieren, wenn diese auftreten. Beispielsweise öffnet John die Department.aspx Seite, klickt auf den Bearbeiten Link für die Geschichtsabteilung und reduziert den Budgetbetrag von 1.000.000,00 $ auf 125.000,00 $. (John verwaltet eine konkurrierende Abteilung und möchte Geld für seine eigene Abteilung freigeben.)

Bild07

Bevor John auf " Aktualisieren" klickt, führt Jane dieselbe Seite aus, klickt auf den Link " Bearbeiten " für die Abteilung "Geschichte", und ändert dann das Feld " Startdatum " vom 1.10.2011 auf 1.1.1999. (Jane verwaltet die Abteilung Geschichte und möchte ihr mehr Seniorität geben.)

Bild08

John klickt zuerst auf "Update ", und dann klickt Jane auf "Update". Janes Browser listet jetzt den Budgetbetrag als $1.000.000.00 auf, aber dies ist falsch, weil der Betrag von John auf $125.000,000 geändert wurde.

Einige der Aktionen, die Sie in diesem Szenario ausführen können, sind die folgenden:

  • Sie können nachverfolgen, welche Eigenschaft ein Benutzer geändert hat und nur die entsprechenden Spalten in der Datenbank aktualisieren. Im Beispielszenario würden keine Daten verloren gehen, da verschiedene Eigenschaften von zwei Benutzern aktualisiert wurden. Wenn jemand das nächste Mal die Abteilung Geschichte durchsucht, wird er 1/1/1999 und $ 125.000,000 sehen.

    Dies ist das Standardverhalten im Entity Framework und kann die Anzahl der Konflikte erheblich verringern, die zu Datenverlust führen könnten. Dieses Verhalten verhindert jedoch keinen Datenverlust, wenn konkurrierende Änderungen an derselben Eigenschaft einer Entität vorgenommen werden. Darüber hinaus ist dieses Verhalten nicht immer möglich; Wenn Sie gespeicherte Prozeduren einem Entitätstyp zuordnen, werden alle Eigenschaften einer Entität aktualisiert, wenn Änderungen an der Entität in der Datenbank vorgenommen werden.

  • Sie können die Änderung von Jane die Änderung von John überschreiben lassen. Nachdem Jane auf "Update" geklickt hat, geht der Budgetbetrag auf 1.000.000,00 $ zurück. Das ist entweder ein Client gewinnt- oder ein Letzter Schreiber gewinnt-Szenario. (Die Werte des Clients haben Vorrang vor dem, was sich im Datenspeicher befindet.)

  • Sie können verhindern, dass Janes Änderung in der Datenbank aktualisiert wird. In der Regel würden Sie eine Fehlermeldung anzeigen, ihr den aktuellen Status der Daten anzeigen und zulassen, dass sie ihre Änderungen erneut eingibt, wenn sie sie noch vornehmen möchte. Sie können den Prozess weiter automatisieren, indem Sie ihre Eingabe speichern und ihr die Möglichkeit geben, sie erneut zu verwenden, ohne sie erneut eingeben zu müssen. Dieses Szenario wird Store-Gewinn genannt. (Die Datenspeicherwerte haben Vorrang vor den vom Client übermittelten Werten.)

Erkennen von Parallelitätskonflikten

Im Entity Framework können Sie Konflikte lösen, indem Sie OptimisticConcurrencyException-Ausnahmen behandeln, die das Entity Framework auslöst. Entity Framework muss dazu in der Lage sein, Konflikte zu erkennen, damit es weiß, wann diese Ausnahmen ausgelöst werden sollen. Aus diesem Grund müssen Sie die Datenbank und das Datenmodell entsprechend konfigurieren. Einige der Optionen für das Aktivieren der Konflikterkennung schließen Folgendes ein:

  • Fügen Sie in der Datenbank eine Tabellenspalte ein, die verwendet werden kann, um zu bestimmen, wann eine Zeile geändert wurde. Anschließend können Sie das Entity Framework so konfigurieren, dass diese Spalte in die Where Klausel von SQL Update oder Delete Befehlen eingeschlossen wird.

    Das ist der Zweck der Timestamp Spalte in der OfficeAssignment Tabelle.

    Bild09

    Der Datentyp der Timestamp-Spalte wird auch als Timestamp bezeichnet. Die Spalte enthält jedoch keinen Datums- oder Uhrzeitwert. Stattdessen ist der Wert eine fortlaufende Zahl, die jedes Mal erhöht wird, wenn die Zeile aktualisiert wird. In einem Update oder Delete Befehl enthält die Where Klausel den ursprünglichen Timestamp Wert. Wenn die Zeile, die aktualisiert wird, von einem anderen Benutzer geändert wurde, unterscheidet sich der Wert von Timestamp dem ursprünglichen Wert, sodass die Where Klausel keine Zeile zurückgibt, die aktualisiert werden soll. Wenn das Entity Framework feststellt, dass vom aktuellen Update Oder Delete Befehl keine Zeilen aktualisiert wurden (d. h. wenn die Anzahl der betroffenen Zeilen null ist), interpretiert es diese als Parallelitätskonflikt.

  • Konfigurieren Sie das Entity Framework so, dass die ursprünglichen Werte jeder Spalte in der Tabelle in der Where Klausel und UpdateDelete befehle enthalten sind.

    Wie bei der ersten Option gibt die Where-Klausel keine Zeile zurück, die aktualisiert werden soll, wenn sich seit dem ersten Lesen etwas in der Zeile geändert hat, was das Entity Framework als Parallelitätskonflikt interpretiert. Diese Methode ist so effektiv wie die Verwendung eines Timestamp Felds, kann aber ineffizient sein. Bei Datenbanktabellen mit vielen Spalten kann dies zu sehr großen Where Klauseln führen, und in einer Webanwendung kann es erforderlich sein, dass Sie große Mengen an Status beibehalten. Die Verwaltung großer Statusmengen kann sich auf die Anwendungsleistung auswirken, da entweder Serverressourcen (z. B. Sitzungszustand) erforderlich sind oder in die Webseite selbst aufgenommen werden müssen (z. B. Ansichtszustand).

In diesem Tutorial fügen Sie die Fehlerbehandlung für optimistische Parallelitätskonflikte sowohl für eine Entität ohne Tracking-Eigenschaft (die Department-Entität) als auch für eine Entität mit einer Tracking-Eigenschaft (die OfficeAssignment-Entität) hinzu.

Umgang mit optimistischer Parallelität ohne eine Tracking-Eigenschaft

Um eine optimistische Parallelität für die Department Entität zu implementieren, die keine Nachverfolgungseigenschaft (Timestamp) aufweist, führen Sie die folgenden Aufgaben aus:

  • Ändern Sie das Datenmodell, um die Parallelitätsnachverfolgung für Department Entitäten zu aktivieren.
  • Behandeln Sie in der SchoolRepository Klasse Parallelitätsausnahmen in der SaveChanges Methode.
  • Behandeln Sie auf der Departments.aspx-Seite Parallelitätsausnahmen, indem Sie dem Benutzer eine Meldung anzeigen, dass die versuchten Änderungen nicht erfolgreich waren. Der Benutzer kann dann die aktuellen Werte sehen und die Änderungen wiederholen, wenn sie noch benötigt werden.

Aktivieren der Parallelitätsnachverfolgung im Datenmodell

Öffnen Sie in Visual Studio die Contoso University-Webanwendung, mit der Sie im vorherigen Lernprogramm dieser Reihe gearbeitet haben.

Öffnen Sie SchoolModel.edmx, und klicken Sie im Datenmodell-Designer mit der rechten Maustaste auf die Name Eigenschaft in der Department Entität, und klicken Sie dann auf Eigenschaften. Ändern Sie im Eigenschaftenfenster die ConcurrencyMode Eigenschaft in Fixed.

Bild16

Gleiches für die anderen Nicht-Primärschlüssel-Skalareigenschaften (Budget, StartDateund Administrator.) (Dies ist für Navigationseigenschaften nicht möglich.) Dadurch wird angegeben, dass diese Spalten (mit ursprünglichen Werten) in der Update Klausel enthalten sein müssen, wenn das Entity Framework einen Delete oder Department SQL-Befehl generiert, um die Where Entität in der Datenbank zu aktualisieren. Wenn beim Ausführen des Update-Befehls keine Zeile Delete gefunden wird, löst das Entity Framework eine OptimisticConcurrencyException aus.

Speichern und schließen Sie das Datenmodell.

Behandlung von Parallelitätsausnahmen im DAL

Öffnen Sie SchoolRepository.cs , und fügen Sie die folgende using Anweisung für den System.Data Namespace hinzu:

using System.Data;

Fügen Sie die folgende neue SaveChanges-Methode hinzu, die optimistische Parallelitätsausnahmen behandelt.

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

Wenn beim Aufrufen dieser Methode ein Parallelitätsfehler auftritt, werden die Eigenschaftswerte der Entität im Arbeitsspeicher durch die werte ersetzt, die sich derzeit in der Datenbank befinden. Die Parallelitäts ausnahme wird erneut gedrosselt, sodass die Webseite sie verarbeiten kann.

Ersetzen Sie in den DeleteDepartment und UpdateDepartment den Methoden den vorhandenen Aufruf context.SaveChanges() durch einen Aufruf SaveChanges() , um die neue Methode aufzurufen.

Behandlung von Parallelitätsausnahmen in der Präsentationsschicht

Öffnen Sie Departments.aspx, und fügen Sie dem OnDeleted="DepartmentsObjectDataSource_Deleted" Steuerelement ein DepartmentsObjectDataSource Attribut hinzu. Der öffnende Tag für das Steuerelement wird nun dem folgenden Beispiel ähneln.

<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" >

Geben Sie im DepartmentsGridView Steuerelement alle Tabellenspalten im DataKeyNames Attribut an, wie im folgenden Beispiel gezeigt. Beachten Sie, dass dadurch sehr große Ansichtszustandsfelder erstellt werden, weshalb die Verwendung eines Nachverfolgungsfelds in der Regel die bevorzugte Methode zum Nachverfolgen von Parallelitätskonflikten ist.

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

Öffnen Sie Departments.aspx.cs , und fügen Sie die folgende using Anweisung für den System.Data Namespace hinzu:

using System.Data;

Fügen Sie die folgende neue Methode hinzu, die Sie aus den Updated und Deleted Ereignishandlern des Datenquellensteuerelements zur Behandlung von Parallelitätsausnahmen aufrufen.

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

Dieser Code überprüft den Ausnahmetyp, und wenn es sich um eine Parallelitäts ausnahme handelt, erstellt der Code dynamisch ein CustomValidator Steuerelement, das wiederum eine Meldung im ValidationSummary Steuerelement anzeigt.

Rufen Sie die neue Methode aus dem Updated Ereignishandler auf, den Sie zuvor hinzugefügt haben. Erstellen Sie außerdem einen neuen Deleted Ereignishandler, der dieselbe Methode aufruft (aber nichts anderes tut):

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

Testen der optimistischen Parallelität auf der Abteilungsseite

Führen Sie die seite Departments.aspx aus.

Screenshot der Seite

Klicken Sie in einer Zeile auf "Bearbeiten ", und ändern Sie den Wert in der Spalte "Budget ". (Denken Sie daran, dass Sie datensätze, die Sie für dieses Lernprogramm erstellt haben, nur bearbeiten können, da die vorhandenen School Datenbankeinträge einige ungültige Daten enthalten. Der Datensatz für die Wirtschaftsabteilung ist ein sicherer, mit dem man experimentieren kann.)

Bild 18

Öffnen Sie ein neues Browserfenster, und führen Sie die Seite erneut aus (kopieren Sie die URL aus dem Adressfeld des ersten Browserfensters in das zweite Browserfenster).

Screenshot eines neuen Browserfensters, das zur Eingabe bereit ist.

Klicken Sie in derselben Zeile, die Sie zuvor bearbeitet haben, auf "Bearbeiten ", und ändern Sie den Wert "Budget" in etwas anderes.

Bild 19

Klicken Sie im zweiten Browserfenster auf "Aktualisieren". Der Budgetbetrag wird erfolgreich in diesen neuen Wert geändert.

Bild20

Klicken Sie im ersten Browserfenster auf "Aktualisieren". Das Update schlägt fehl. Der Budgetbetrag wird mithilfe des Werts, den Sie im zweiten Browserfenster festgelegt haben, erneut angezeigt, und es wird eine Fehlermeldung angezeigt.

Bild21

Behandeln optimistischer Parallelität mithilfe einer Tracking-Eigenschaft

Um optimistische Parallelität für eine Entität mit einer Tracking-Eigenschaft zu behandeln, führen Sie die folgenden Aufgaben aus:

  • Fügen Sie dem Datenmodell gespeicherte Prozeduren zum Verwalten von OfficeAssignment Entitäten hinzu. (Die Nachverfolgung von Eigenschaften und gespeicherten Prozeduren muss nicht zusammen verwendet werden; sie sind hier nur zur Veranschaulichung gruppiert.)
  • Fügen Sie CRUD-Methoden dem DAL und der BLL für OfficeAssignment Entitäten hinzu, einschließlich Code zum Behandeln optimistischer Parallelitätsausnahmen im DAL.
  • Erstellen Sie eine Office-Aufgaben-Webseite.
  • Testen Sie die optimistische Parallelität auf unserer neuen Webseite.

Hinzufügen gespeicherter OfficeAssignment-Prozeduren zum Datenmodell

Öffnen Sie die Datei SchoolModel.edmx im Modell-Designer, klicken Sie mit der rechten Maustaste auf die Entwurfsoberfläche, und klicken Sie auf "Modell aus Datenbank aktualisieren". Erweitern Sie auf der Registerkarte " Hinzufügen " im Dialogfeld " Datenbankobjekte auswählen " gespeicherte Prozeduren , und wählen Sie die drei OfficeAssignment gespeicherten Prozeduren aus (siehe den folgenden Screenshot), und klicken Sie dann auf "Fertig stellen". (Diese gespeicherten Prozeduren waren bereits in der Datenbank enthalten, als Sie sie mithilfe eines Skripts heruntergeladen oder erstellt haben.)

Bild02

Klicken Sie mit der rechten Maustaste auf die OfficeAssignment Entität, und wählen Sie " Gespeicherte Prozedurzuordnung" aus.

Bild03

Legen Sie die Funktionen "Einfügen", " Aktualisieren" und " Löschen " fest, um die entsprechenden gespeicherten Prozeduren zu verwenden. Legen Sie für den OrigTimestamp-Parameter der Update-Funktion die Eigenschaft auf Timestamp fest und wählen Sie die Option Originalwert verwenden aus.

Bild04

Wenn das Entity Framework die UpdateOfficeAssignment gespeicherte Prozedur aufruft, wird der ursprüngliche Wert der Timestamp Spalte im OrigTimestamp Parameter übergeben. Die gespeicherte Prozedur verwendet diesen Parameter in seiner Where Klausel:

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

Die gespeicherte Prozedur wählt auch den neuen Wert der Timestamp Spalte nach der Aktualisierung aus, sodass das Entity Framework die OfficeAssignment Entität, die sich im Arbeitsspeicher befindet, mit der entsprechenden Datenbankzeile synchronisieren kann.

(Beachten Sie, dass die gespeicherte Prozedur zum Löschen einer Office-Zuweisung keinen Parameter hat OrigTimestamp . Aus diesem Grund kann das Entity Framework nicht überprüfen, ob eine Entität vor dem Löschen unverändert ist.)

Speichern und schließen Sie das Datenmodell.

Hinzufügen von OfficeAssignment-Methoden zum DAL

Öffnen Sie ISchoolRepository.cs , und fügen Sie die folgenden CRUD-Methoden für den Entitätssatz OfficeAssignment hinzu:

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

Fügen Sie die folgenden neuen Methoden zum SchoolRepository.cs hinzu. In der UpdateOfficeAssignment Methode rufen Sie die lokale SaveChanges Methode anstelle von 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();
}

Öffnen Sie im Testprojekt MockSchoolRepository.cs , und fügen Sie die folgenden OfficeAssignment Sammlungs- und CRUD-Methoden hinzu. (Das Pseudo-Repository muss die Repositoryschnittstelle implementieren, oder die Lösung wird nicht kompiliert.)

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

Hinzufügen von OfficeAssignment-Methoden zur BLL

Öffnen Sie im Hauptprojekt SchoolBL.cs , und fügen Sie die folgenden CRUD-Methoden für die Entität hinzu, die OfficeAssignment darauf festgelegt ist:

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

Erstellen einer OfficeAssignments-Webseite

Erstellen Sie eine neue Webseite, die die Gestaltungsvorlage "Site.Master " verwendet, und nennen Sie sie OfficeAssignments.aspx. Fügen Sie dem Content-Steuerelement, das Content2 genannt wird, das folgende Markup hinzu:

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

Beachten Sie, dass im DataKeyNames Attribut das Markup die Timestamp Eigenschaft sowie den Datensatzschlüssel (InstructorID) angibt. Durch das Angeben von Eigenschaften im DataKeyNames Attribut wird das Steuerelement im Steuerelementzustand (ähnlich dem Ansichtszustand) gespeichert, sodass die ursprünglichen Werte während der Postbackverarbeitung verfügbar sind.

Wenn Sie den Timestamp Wert nicht gespeichert haben, hätte das Entity Framework ihn für die Where Klausel des SQL-Befehls Update nicht. Folglich würde nichts zum Aktualisieren gefunden werden. Daher würde das Entity Framework bei jeder Aktualisierung einer OfficeAssignment Entität eine optimistische Parallelitäts ausnahme auslösen.

Öffnen Sie OfficeAssignments.aspx.cs , und fügen Sie die folgende using Anweisung für die Datenzugriffsebene hinzu:

using ContosoUniversity.DAL;

Fügen Sie die folgende Page_Init Methode hinzu, mit der dynamische Datenfunktionen aktiviert werden. Fügen Sie außerdem den folgenden Handler für das Ereignis des ObjectDataSource Steuerelements Updated hinzu, um auf Parallelitätsfehler zu überprüfen:

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

Testen der optimistischen Parallelität auf der OfficeAssignments-Seite

Führen Sie die Seite OfficeAssignments.aspx aus.

Screenshot der Seite

Klicken Sie in einer Zeile auf "Bearbeiten ", und ändern Sie den Wert in der Spalte " Speicherort ".

Bild11

Öffnen Sie ein neues Browserfenster, und führen Sie die Seite erneut aus (kopieren Sie die URL aus dem ersten Browserfenster in das zweite Browserfenster).

Screenshot eines neuen Browserfensters.

Klicken Sie in derselben Zeile, die Sie zuvor bearbeitet haben, auf "Bearbeiten ", und ändern Sie den Wert "Location " in etwas anderes.

Bild12

Klicken Sie im zweiten Browserfenster auf "Aktualisieren".

Bild13

Wechseln Sie zum ersten Browserfenster, und klicken Sie auf "Aktualisieren".

Bild 15

Es wird eine Fehlermeldung angezeigt, und der Wert "Location " wurde aktualisiert, um den Wert anzuzeigen, in den Sie ihn im zweiten Browserfenster geändert haben.

Verwalten der Parallelität mit dem EntityDataSource-Steuerelement

Das EntityDataSource Steuerelement enthält integrierte Logik, die die Parallelitätseinstellungen im Datenmodell erkennt und Aktualisierungs- und Löschvorgänge entsprechend behandelt. Wie bei allen anderen Ausnahmen müssen Sie jedoch OptimisticConcurrencyException Ausnahmen selbst behandeln, um eine benutzerfreundliche Fehlermeldung bereitzustellen.

Als Nächstes konfigurieren Sie die Courses.aspx Seite (die ein EntityDataSource Steuerelement verwendet), um Aktualisierungs- und Löschvorgänge zuzulassen und eine Fehlermeldung anzuzeigen, wenn ein Parallelitätskonflikt auftritt. Die Course Entität verfügt nicht über eine Parallelitätsnachverfolgungsspalte. Daher verwenden Sie dieselbe Methode, die Sie mit der Department Entität ausgeführt haben: Verfolgen Sie die Werte aller nicht schlüsselfreien Eigenschaften.

Öffnen Sie die Datei SchoolModel.edmx . Legen Sie für die Nichtschlüsseleigenschaften der Course Entität (Title, Creditsund DepartmentID), die Concurrency Mode-Eigenschaft auf Fixed. Speichern und schließen Sie dann das Datenmodell.

Öffnen Sie die Courses.aspx Seite, und nehmen Sie die folgenden Änderungen vor:

  • Fügen Sie im CoursesEntityDataSource-Steuerelement die EnableUpdate="true"- und EnableDelete="true"-Attribute hinzu. Das öffnende Element für dieses Steuerelement sieht nun wie im folgenden Beispiel aus:

    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" 
            AutoGenerateWhereClause="True" EntitySetName="Courses"
            EnableUpdate="true" EnableDelete="true">
    
  • Ändern Sie im CoursesGridView Steuerelement den DataKeyNames Attributwert in "CourseID,Title,Credits,DepartmentID". Fügen Sie dann dem CommandField Element ein Columns Element hinzu, das die Schaltflächen "Bearbeiten" und "Löschen" (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />) anzeigt. Das GridView Steuerelement ähnelt nun dem folgenden Beispiel:

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

Rufen Sie die Seite auf und verursachen Sie eine Konfliktsituation wie zuvor auf der Seite "Abteilungen". Führen Sie die Seite in zwei Browserfenstern aus, klicken Sie in den einzelnen Fenstern auf "Bearbeiten" in derselben Zeile, und nehmen Sie jeweils eine andere Änderung vor. Klicken Sie in einem Fenster auf "Aktualisieren ", und klicken Sie dann im anderen Fenster auf "Aktualisieren ". Wenn Sie zum zweiten Mal auf "Aktualisieren" klicken, wird die Fehlerseite angezeigt, die aus einer Ausnahme für nicht behandelte Parallelität resultiert.

Bild22

Sie behandeln diesen Fehler auf eine Weise, die sehr ähnlich ist, wie Sie ihn für das ObjectDataSource Steuerelement behandelt haben. Öffnen Sie die Courses.aspx Seite, und geben Sie im CoursesEntityDataSource Steuerelement Handler für die Deleted und Updated Ereignisse an. Das Start-Tag des Steuerelements ähnelt nun dem folgenden Beispiel:

<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">

Fügen Sie vor dem CoursesGridView Steuerelement das folgende ValidationSummary Steuerelement hinzu:

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

Fügen Sie in Courses.aspx.cs eine using Anweisung für den System.Data Namespace hinzu, fügen Sie eine Methode hinzu, die auf Parallelitätsausnahmen prüft, und fügen Sie Handler für das EntityDataSource-Steuerelement und die Updated- und Deleted-Handler hinzu. Der Code sieht wie folgt aus:

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

Der einzige Unterschied zwischen diesem Code und dem, was Sie für das ObjectDataSource Steuerelement getan haben, besteht darin, dass sich in diesem Fall die Parallelitäts ausnahme in der Exception Eigenschaft des Ereignisargumentobjekts InnerException und nicht in der Eigenschaft dieser Ausnahme befindet.

Führen Sie die Seite aus, und erstellen Sie erneut einen Parallelitätskonflikt. Dieses Mal wird eine Fehlermeldung angezeigt:

Bild23

Damit ist die Einführung in die Behandlung von Nebenläufigkeitskonflikten abgeschlossen. Das nächste Lernprogramm enthält Anleitungen zur Verbesserung der Leistung in einer Webanwendung, die das Entity Framework verwendet.