Behandeln der Parallelität mit dem Entity Framework 4.0 in einer ASP.NET 4-Webanwendung
von Tom Dykstra
Diese Tutorialreihe baut auf der Webanwendung der Contoso University auf, die vom Erste Schritte mit der Tutorialreihe Entity Framework 4.0 erstellt wird. Wenn Sie die vorherigen Tutorials nicht abgeschlossen haben, können Sie als Ausgangspunkt für dieses Tutorial die Anwendung herunterladen, die Sie erstellt hätten. Sie können auch die Anwendung herunterladen, die von der vollständigen Tutorialreihe erstellt wird. Wenn Sie Fragen zu den Tutorials haben, können Sie diese im ASP.NET Entity Framework-Forum veröffentlichen.
Im vorherigen Tutorial haben Sie gelernt, wie Sie Daten mithilfe des Steuerelements ObjectDataSource
und des Entity Framework sortieren und filtern. In diesem Tutorial werden Optionen für die Behandlung von Parallelität in einer ASP.NET-Webanwendung gezeigt, die Entity Framework verwendet. Sie erstellen eine neue Webseite, die der Aktualisierung von Kursleiter-Office-Aufgaben gewidmet ist. Sie behandeln Parallelitätsprobleme auf dieser Seite und auf der Seite "Abteilungen", die Sie zuvor erstellt haben.
Nebenläufigkeitskonflikte
Ein Parallelitätskonflikt tritt auf, wenn ein Benutzer einen Datensatz 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 verarbeitet werden. (Wenn es nur wenige Benutzer oder wenige Updates gibt oder wenn einige Änderungen nicht wirklich kritisch sind, wenn einige Änderungen überschrieben werden, können die Kosten für die Programmierung für Die Parallelität den Vorteil überwiegen.) Wenn Sie sich keine Sorgen um Parallelitätskonflikte machen müssen, können Sie dieses Tutorial überspringen. Die restlichen beiden Tutorials in der Reihe hängen nicht von den in diesem Artikel erstellten Tutorials ab.
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.
Die Verwaltung von Sperren hat einige Nachteile. Es kann komplex sein, sie zu programmieren. Es erfordert erhebliche Datenbankverwaltungsressourcen, und es kann Leistungsprobleme verursachen, wenn die Anzahl der Benutzer einer Anwendung zunimmt (das heißt, sie wird nicht gut skaliert). Aus diesen Gründen unterstützen nicht alle Datenbankverwaltungssysteme die pessimistische Parallelität. Entity Framework bietet keine integrierte Unterstützung dafür, und in diesem Tutorial wird nicht gezeigt, wie Sie es implementieren.
Optimistische Nebenläufigkeit
Die Alternative zur pessimistischen Parallelität ist eine optimistische Parallelität. Die Verwendung der optimistischen Parallelität bedeutet, Nebenläufigkeitskonflikte zu erlauben und entsprechend zu reagieren, wenn diese auftreten. John führt beispielsweise die Seite Department.aspx aus, klickt auf den Link Bearbeiten für die Verlaufsabteilung 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.)
Bevor John auf Aktualisieren klickt, führt Jane dieselbe Seite aus, klickt auf den Link Bearbeiten für die Abteilung Verlauf und ändert dann das Feld Startdatum vom 10.1.2011 in den 1.1.1.1999. (Jane verwaltet die Geschichtsabteilung und möchte ihr mehr Seniorität geben.)
John klickt zuerst auf Aktualisieren , dann auf Aktualisieren. Janes Browser listet den Budgetbetrag jetzt mit $1.000.000,00 auf, aber dies ist falsch, da der Betrag von John in $125.000,00 geändert wurde.
Zu den Aktionen, die Sie in diesem Szenario ausführen können, gehören 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 Geschichtsabteilung durchsucht, wird der 1.1.1999 und 125.000,00 USD angezeigt.
Dies ist das Standardverhalten im Entity Framework und kann die Anzahl von Konflikten, die zu Datenverlust führen können, erheblich reduzieren. Dieses Verhalten vermeidet 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 Janes Änderung johns Änderung überschreiben lassen. Nachdem Jane auf Update geklickt hat, geht der Budgetbetrag auf 1.000.000,00 USD 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 ihr erlauben, ihre Änderungen erneut einzugeben, wenn sie sie weiterhin vornehmen möchte. Sie könnten 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 Speicher gewinnt genannt. (Die Werte des Datenspeichers haben Vorrang gegenüber den Werten, die vom Client gesendet werden).
Erkennen von Parallelitätskonflikten
Im Entity Framework können Sie Konflikte lösen, indem Sie Ausnahmen behandeln OptimisticConcurrencyException
, die vom Entity Framework ausgelöst werden. 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 die Datenbank eine Tabellenspalte ein, mit der ermittelt werden kann, wann eine Zeile geändert wurde. Anschließend können Sie das Entity Framework so konfigurieren, dass diese Spalte in die
Where
SQLUpdate
- oderDelete
-Befehle-Klausel eingeschlossen wird.Das ist der Zweck der
Timestamp
Spalte in derOfficeAssignment
Tabelle.Der Datentyp der
Timestamp
Spalte wird auch genanntTimestamp
. Die Spalte enthält jedoch keinen Datums- oder Uhrzeitwert. Stattdessen ist der Wert eine sequenzielle Zahl, die jedes Mal erhöht wird, wenn die Zeile aktualisiert wird. In einemUpdate
- oderDelete
-Befehl enthält dieWhere
-Klausel den ursprünglichenTimestamp
Wert. Wenn die zeile, die aktualisiert wird, von einem anderen Benutzer geändert wurde, unterscheidet sich der Wert inTimestamp
vom ursprünglichen Wert, sodass dieWhere
-Klausel keine zu aktualisierende Zeile zurückgibt. Wenn Entity Framework feststellt, dass keine Zeilen durch den aktuellenUpdate
Befehl oderDelete
den Befehl aktualisiert wurden (d. h. wenn die Anzahl der betroffenen Zeilen 0 ist) interpretiert es dies als Parallelitätskonflikt.Konfigurieren Sie Entity Framework so, dass die ursprünglichen Werte jeder Spalte in der Tabelle in die -Klausel und
Update
Delete
-Where
Befehle eingeschlossen werden.Wie bei der ersten Option gibt die Klausel keine zu aktualisierende Zeile zurück,
Where
wenn sich etwas in der Zeile seit dem ersten Lesen geändert hat, was vom Entity Framework als Parallelitätskonflikt interpretiert wird. Diese Methode ist genauso effektiv wie die Verwendung eines FeldsTimestamp
, kann aber ineffizient sein. Für Datenbanktabellen mit vielen Spalten kann dies zu sehr großenWhere
Klauseln führen, und in einer Webanwendung kann es erforderlich sein, dass Sie große Mengen an Zustand beibehalten. Das Beibehalten großer Mengen an Zustand kann sich auf die Anwendungsleistung auswirken, da sie entweder Serverressourcen erfordert (z. B. Sitzungszustand) oder in die Webseite selbst einbezogen werden muss (z. B. Ansichtszustand).
In diesem Tutorial fügen Sie die Fehlerbehandlung für optimistische Parallelitätskonflikte für eine Entität ohne Überwachungseigenschaft (die Department
Entität) und für eine Entität mit einer Nachverfolgungseigenschaft (die OfficeAssignment
Entität) hinzu.
Behandeln von optimistischer Parallelität ohne Nachverfolgungseigenschaft
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 derSaveChanges
-Methode. - Behandeln Sie auf der Seite Departments.aspx Parallelitätsausnahmen, indem Sie eine Meldung an den Benutzer anzeigen, dass die versuchten Änderungen nicht erfolgreich waren. Der Benutzer kann dann die aktuellen Werte anzeigen 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 Tutorial 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 Fenster Eigenschaften die ConcurrencyMode
-Eigenschaft in Fixed
.
Führen Sie die gleichen Schritte für die anderen skalaren Eigenschaften ohne Primärschlüssel (Budget
, StartDate
und ) aus Administrator
. (Für Navigationseigenschaften ist dies nicht möglich.) Dies gibt an, dass diese Spalten (mit ursprünglichen Werten) in die Klausel einbezogen werden müssen, wenn Entity Framework einen Update
oder Delete
SQL-Befehl generiert, um die Department
Entität in der Where
Datenbank zu aktualisieren. Wenn beim Ausführen des Update
Befehls oder Delete
keine Zeile gefunden wird, löst das Entity Framework eine Ausnahme für optimistische Parallelität aus.
Speichern und schließen Sie das Datenmodell.
Behandeln von Parallelitätsausnahmen in der 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ätsausnahme wird erneut ausgeführt, damit die Webseite sie behandeln kann.
Ersetzen Sie in den DeleteDepartment
Methoden und UpdateDepartment
den vorhandenen Aufruf von context.SaveChanges()
durch einen Aufruf von SaveChanges()
, um die neue Methode aufzurufen.
Behandeln von Parallelitätsausnahmen auf der Präsentationsebene
Öffnen Sie Departments.aspx, und fügen Sie dem DepartmentsObjectDataSource
Steuerelement ein OnDeleted="DepartmentsObjectDataSource_Deleted"
Attribut hinzu. Das öffnende Tag für das Steuerelement ähnelt nun dem folgenden Beispiel.
<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, was einer der Gründe ist, warum die Verwendung eines Nachverfolgungsfelds im Allgemeinen 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 Ereignishandlern und Deleted
des Datenquellensteuerelements Updated
aufrufen, um Parallelitätsausnahmen zu behandeln:
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ätsausnahme handelt, erstellt der Code dynamisch ein CustomValidator
Steuerelement, das wiederum eine Meldung im ValidationSummary
Steuerelement anzeigt.
Rufen Sie die neue Methode aus dem Ereignishandler auf, den Updated
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.
Klicken Sie in einer Zeile auf Bearbeiten , und ändern Sie den Wert in der Spalte Budget . (Denken Sie daran, dass Sie nur Datensätze bearbeiten können, die Sie für dieses Tutorial erstellt haben, da die vorhandenen School
Datenbankdatensätze einige ungültige Daten enthalten. Der Datensatz für die Wirtschaftsabteilung ist ein sicherer, mit dem man experimentieren kann.)
Ö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).
Klicken Sie in derselben Zeile auf Bearbeiten , die Sie zuvor bearbeitet haben, und ändern Sie den Wert Budget in einen anderen Wert.
Klicken Sie im zweiten Browserfenster auf Aktualisieren. Der Budgetbetrag wurde erfolgreich in diesen neuen Wert geändert.
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.
Behandeln von optimistischer Parallelität mithilfe einer Nachverfolgungseigenschaft
Um die optimistische Parallelität für eine Entität mit einer Überwachungseigenschaft zu behandeln, führen Sie die folgenden Aufgaben aus:
- Fügen Sie dem Datenmodell gespeicherte Prozeduren hinzu, um Entitäten zu verwalten
OfficeAssignment
. (Überwachungseigenschaften und gespeicherte Prozeduren müssen nicht zusammen verwendet werden, sie werden hier nur zur Veranschaulichung gruppiert.) - Fügen Sie der DAL und der BLL für
OfficeAssignment
Entitäten CRUD-Methoden hinzu, einschließlich Code zur Behandlung von Ausnahmen für optimistische Parallelität in der DAL. - Erstellen Sie eine Webseite mit Office-Zuweisungen.
- Testen Sie die optimistische Parallelität auf der 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 des Dialogfelds Datenbankobjekte auswählen den Eintrag Gespeicherte Prozeduren , wählen Sie die drei OfficeAssignment
gespeicherten Prozeduren aus (siehe folgenden Screenshot), und klicken Sie dann auf Fertig stellen. (Diese gespeicherten Prozeduren befanden sich bereits in der Datenbank, als Sie sie heruntergeladen oder mithilfe eines Skripts erstellt haben.)
Klicken Sie mit der rechten Maustaste auf die OfficeAssignment
Entität, und wählen Sie Zuordnung gespeicherter Prozeduren aus.
Legen Sie die Funktionen Einfügen, Aktualisieren und Löschen so fest, dass die entsprechenden gespeicherten Prozeduren verwendet werden. Legen Sie für den OrigTimestamp
Parameter der Update
Funktion die Eigenschaft auf Timestamp
fest, und wählen Sie die Option Originalwert verwenden aus.
Wenn 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 ihrer 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 synchron halten kann.
(Beachten Sie, dass die gespeicherte Prozedur zum Löschen einer Office-Zuweisung keinen Parameter enthält OrigTimestamp
. Aus diesem Grund kann das Entity Framework nicht überprüfen, ob eine Entität unverändert ist, bevor sie gelöscht wird.)
Speichern und schließen Sie das Datenmodell.
Hinzufügen von OfficeAssignment-Methoden zur 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 SchoolRepository.cs die folgenden neuen Methoden hinzu. In der UpdateOfficeAssignment
-Methode rufen Sie die lokale SaveChanges
Methode anstelle von auf 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
Auflistungs- und CRUD-Methoden hinzu. (Das Pseudorepository muss die Repositoryschnittstelle implementieren, sonst wird die Lösung 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 Standard-Projekt SchoolBL.cs, und fügen Sie die folgenden CRUD-Methoden für die Entität hinzu, die OfficeAssignment
ihr zugewiesen 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 Seite Site.Master master verwendet, und nennen Sie sie OfficeAssignments.aspx. Fügen Sie dem Steuerelement das folgende Markup mit dem Content
Namen hinzu Content2
:
<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 das Markup im DataKeyNames
Attribut die Timestamp
Eigenschaft sowie den Datensatzschlüssel (InstructorID
) angibt. Das Angeben von DataKeyNames
Eigenschaften im -Attribut bewirkt, dass das Steuerelement sie im Steuerelementzustand speichert (ähnlich dem Ansichtszustand), sodass die ursprünglichen Werte während der Postbackverarbeitung verfügbar sind.
Wenn Sie den Timestamp
Wert nicht gespeichert haben, hat das Entity Framework ihn nicht für die Where
Klausel des SQL-Befehls Update
. Folglich würde nichts aktualisiert werden. Infolgedessen löst das Entity Framework bei jeder Aktualisierung einer Entität eine optimistische Parallelitätsausnahme aus OfficeAssignment
.
Ö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, die Dynamic Data-Funktionalität aktiviert. Fügen Sie auch den folgenden Handler für das ObjectDataSource
-Ereignis des Steuerelements Updated
hinzu, um nach Parallelitätsfehlern zu suchen:
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.
Klicken Sie auf In zeile bearbeiten , und ändern Sie den Wert in der Spalte Speicherort .
Ö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).
Klicken Sie in derselben Zeile, die Sie zuvor bearbeitet haben, auf Bearbeiten , und ändern Sie den Wert Location in einen anderen Wert.
Klicken Sie im zweiten Browserfenster auf Aktualisieren.
Wechseln Sie zum ersten Browserfenster, und klicken Sie auf Aktualisieren.
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.
Behandeln von Parallelität mit dem EntityDataSource-Steuerelement
Das EntityDataSource
Steuerelement enthält eine integrierte Logik, die die Parallelitätseinstellungen im Datenmodell erkennt und Aktualisierungs- und Löschvorgänge entsprechend verarbeitet. Wie bei allen Ausnahmen müssen Sie ausnahmen jedoch selbst behandeln OptimisticConcurrencyException
, um eine benutzerfreundliche Fehlermeldung bereitzustellen.
Als Nächstes konfigurieren Sie die Seite Courses.aspx (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, sodass Sie dieselbe Methode wie bei der Department
Entität verwenden: Nachverfolgen der Werte aller Nichtschlüsseleigenschaften.
Öffnen Sie die Datei SchoolModel.edmx . Legen Sie für die Nichtschlüsseleigenschaften der Course
Entität (Title
, Credits
und DepartmentID
) die Eigenschaft Parallelitätsmodus auf fest Fixed
. Speichern und schließen Sie dann das Datenmodell.
Öffnen Sie die Seite Courses.aspx , und nehmen Sie die folgenden Änderungen vor:
Fügen Sie im
CoursesEntityDataSource
Steuerelement Attribute hinzuEnableUpdate="true"
.EnableDelete="true"
Das öffnende Tag für dieses Steuerelement ä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">
Ändern Sie im
CoursesGridView
Steuerelement denDataKeyNames
Attributwert in"CourseID,Title,Credits,DepartmentID"
. Fügen Sie dann dem Element einCommandField
Element hinzu, das dieColumns
Schaltflächen Bearbeiten und Löschen (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
) anzeigt. DasGridView
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>
Führen Sie die Seite aus, und erstellen Sie eine Konfliktsituation wie zuvor auf der Seite "Abteilungen". Führen Sie die Seite in zwei Browserfenstern aus, klicken Sie in der gleichen Zeile in jedem Fenster auf Bearbeiten , und nehmen Sie in jedem Fenster eine andere Änderung vor. Klicken Sie in einem Fenster auf Aktualisieren und dann im anderen Fenster auf Aktualisieren . Wenn Sie zum zweiten Mal auf Aktualisieren klicken, wird die Fehlerseite angezeigt, die sich aus einer Ausnahme für nicht behandelte Parallelität ergibt.
Sie behandeln diesen Fehler in einer Weise, die der Behandlung für das ObjectDataSource
Steuerelement sehr ähnlich ist. Öffnen Sie die Seite Courses.aspx , und geben Sie im CoursesEntityDataSource
-Steuerelement Handler für die Deleted
Ereignisse und Updated
an. Das öffnende 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 überprüft, und fügen Sie Handler für die Handler und Updated
Deleted
des EntityDataSource
Steuerelements 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 gemacht haben, besteht darin, dass die Parallelitätsausnahme in diesem Fall in der Exception
-Eigenschaft des Ereignisargumentobjekts und nicht in der -Eigenschaft dieser InnerException
Ausnahme liegt.
Führen Sie die Seite aus, und erstellen Sie erneut einen Parallelitätskonflikt. Dieses Mal wird eine Fehlermeldung angezeigt:
Damit ist die Einführung in die Behandlung von Nebenläufigkeitskonflikten abgeschlossen. Das nächste Tutorial enthält Anleitungen zum Verbessern der Leistung in einer Webanwendung, die Entity Framework verwendet.