Freigeben über


Behandeln von Parallelität mit dem Entity Framework in einer ASP.NET MVC-Anwendung (7 von 10)

Von Tom Dykstra

Die Contoso University-Beispielwebanwendung veranschaulicht, wie ASP.NET MVC 4-Anwendungen mithilfe von Entity Framework 5 Code First und Visual Studio 2012 erstellt werden. Informationen zu dieser Tutorialreihe finden Sie im ersten Tutorial der Reihe.

Hinweis

Wenn ein Problem auftritt, das Sie nicht beheben können, laden Sie das fertige Kapitel herunter, und versuchen Sie, Ihr Problem zu reproduzieren. In der Regel finden Sie die Lösung für das Problem, indem Sie Ihren Code mit dem fertigen Code vergleichen. Einige häufige Fehler und deren Lösung finden Sie unter "Fehler und Problemumgehungen".

In den vorherigen beiden Lernprogrammen haben Sie mit verwandten Daten gearbeitet. In diesem Lernprogramm wird gezeigt, wie Die Parallelität behandelt wird. Sie erstellen Webseiten, die mit der Department Entität arbeiten, und die Seiten, die Entitäten bearbeiten und löschen Department , behandeln Parallelitätsfehler. Die folgenden Abbildungen zeigen die Seiten "Index" und "Löschen", einschließlich einiger Nachrichten, die angezeigt werden, wenn ein Parallelitätskonflikt auftritt.

Screenshot der Seite

Screenshot der Seite

Nebenläufigkeitskonflikte

Ein Parallelitätskonflikt tritt auf, wenn ein Benutzer die Daten einer Entität anzeigt, um diese zu bearbeiten, und ein anderer Benutzer eben diese Entitätsdaten aktualisiert, bevor die Änderungen des ersten Benutzers in die Datenbank geschrieben wurden. Wenn Sie die Erkennung solcher Konflikte nicht aktivieren, überschreibt das letzte Update der Datenbank die Änderungen des anderen Benutzers. In vielen Anwendungen ist dieses Risiko akzeptabel: Wenn es nur wenige Benutzer bzw. wenige Updates gibt, oder wenn es nicht schlimm ist, dass Änderungen überschrieben werden können, ist es den Aufwand, für die Parallelität zu programmieren, möglicherweise nicht wert. In diesem Fall müssen Sie für die Anwendung keine Behandlung von Nebenläufigkeitskonflikten konfigurieren.

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 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 Nebenläufigkeit

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 führt John die Seite "Abteilungen bearbeiten" aus, ändert den Budgetbetrag für die englische Abteilung von 350.000,00 $ auf 0,00 $.

Changing_English_dept_budget_to_100000

Bevor John auf "Speichern" klickt, führt Jane dieselbe Seite aus und ändert das Feld "Startdatum" vom 1.09.2007 auf 8.08.2013.

Changing_English_dept_start_date_to_1999

John klickt zuerst auf "Speichern ", und sieht seine Änderung, wenn der Browser zur Indexseite zurückkehrt, und dann klickt Jane auf "Speichern". Was daraufhin geschieht, ist abhängig davon, wie Sie Nebenläufigkeitskonflikte behandeln. Einige der Optionen schließen Folgendes ein:

  • 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 englische Abteilung durchsucht, werden sowohl John es als auch Janes Änderungen angezeigt – ein Startdatum vom 8.8.2013 und ein Budget von Null Dollar.

    Diese Methode der Aktualisierung kann die Anzahl von Konflikten reduzieren, die zu Datenverlusten führen können. Sie kann Datenverluste jedoch nicht verhindern, wenn konkurrierende Änderungen an der gleichen Eigenschaft einer Entität vorgenommen werden. Ob Entity Framework auf diese Weise funktioniert, hängt davon ab, wie Sie Ihren Aktualisierungscode implementieren. Oft ist dies in Webanwendungen nicht praktikabel, da es erforderlich sein kann, viele Zustände zu verwalten, um alle ursprünglichen Eigenschaftswerte einer Entität und die neuen Werte im Auge zu behalten. Die Aufrechterhaltung großer Statusmengen kann sich auf die Anwendungsleistung auswirken, da entweder Serverressourcen erforderlich sind oder in die Webseite selbst eingeschlossen werden müssen (z. B. in ausgeblendeten Feldern).

  • Sie können die Änderung von Janes Änderung überschreiben lassen. Wenn jemand das nächste Mal die englische Abteilung durchsucht, wird der Wert 8.8.2013 und der wiederhergestellte Wert 350.000,00 $ angezeigt. 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.) Wie in der Einführung in diesen Abschnitt erwähnt, geschieht dies automatisch, wenn Sie keine Codierung für die Parallelitätsbehandlung ausführen.

  • 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 anwenden kann, wenn sie sie trotzdem vornehmen möchte. Dieses Szenario wird Speicher gewinnt genannt. (Die Werte des Datenspeichers haben Vorrang vor den Werten, die vom Client gesendet werden.) In diesem Tutorial implementieren Sie das Szenario „Speicher gewinnt“. Diese Methode stellt sicher, dass keine Änderung überschrieben wird, ohne dass ein Benutzer darüber benachrichtigt wird.

Erkennen von Parallelitätskonflikten

Sie können Konflikte lösen, indem Sie optimistischeConcurrencyException-Ausnahmen behandeln, 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 eine Änderungsverfolgungsspalte in die Datenbanktabelle ein, die verwendet werden kann, um zu bestimmen, wenn eine Änderung an einer Zeile vorgenommen 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.

    Der Datentyp der Nachverfolgungsspalte ist in der Regel rowversion. Der Rowversion-Wert ist eine fortlaufende Zahl, die jedes Mal erhöht wird, wenn die Zeile aktualisiert wird. In einer oder Delete einem Update Befehl enthält die Where Klausel den ursprünglichen Wert der Nachverfolgungsspalte (die ursprüngliche Zeilenversion). Wenn die Zeile, die aktualisiert wird, von einem anderen Benutzer geändert wurde, unterscheidet sich der Wert in der rowversion Spalte von dem ursprünglichen Wert, sodass die Update Zeile oder Delete Anweisung aufgrund der Where Klausel nicht gefunden werden kann. Wenn das Entity Framework feststellt, dass keine Zeilen durch den Befehl oder Delete den Update Befehl 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 Update Delete befehle enthalten sind.

    Wie bei der ersten Option gibt die Klausel, wenn sich etwas in der Zeile seit dem ersten Lesen geändert hat, Where keine Zeile zurück, die aktualisiert werden soll, was das Entity Framework als Parallelitätskonflikt interpretiert. Bei Datenbanktabellen mit vielen Spalten kann dieser Ansatz zu sehr großen Where Klauseln führen und erfordern, dass Sie große Mengen an Zustand beibehalten. Wie bereits erwähnt, kann die Aufrechterhaltung großer Mengen an Zustand die Anwendungsleistung beeinträchtigen, da entweder Serverressourcen erforderlich sind oder in die Webseite selbst aufgenommen werden müssen. Daher wird dieser Ansatz in der Regel nicht empfohlen, und es ist nicht die Methode, die in diesem Lernprogramm verwendet wird.

    Wenn Sie diesen Ansatz für Parallelität implementieren möchten, müssen Sie alle Nicht-Primärschlüssel-Eigenschaften in der Entität markieren, für die Sie die Parallelität nachverfolgen möchten, indem Sie das ConcurrencyCheck-Attribut hinzufügen. Diese Änderung ermöglicht es dem Entity Framework, alle Spalten in die SQL-Klausel WHERE von UPDATE Anweisungen einzuschließen.

Im restlichen Teil dieses Lernprogramms fügen Sie der Department Entität eine Rowversion-Tracking-Eigenschaft hinzu, erstellen einen Controller und Ansichten, und testen Sie, ob alles ordnungsgemäß funktioniert.

Hinzufügen einer optimistischen Parallelitätseigenschaft zur Abteilungsentität

Fügen Sie in Models\Department.cs eine Tracking-Eigenschaft mit dem Namen hinzu RowVersion:

public class Department
{
    public int DepartmentID { get; set; }

    [StringLength(50, MinimumLength = 3)]
    public string Name { get; set; }

    [DataType(DataType.Currency)]
    [Column(TypeName = "money")]
    public decimal Budget { get; set; }

    [DataType(DataType.Date)]
    public DateTime StartDate { get; set; }

    [Display(Name = "Administrator")]
    public int? InstructorID { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }

    public virtual Instructor Administrator { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}

Das Timestamp-Attribut gibt an, dass diese Spalte in die Where Klausel und Update Delete Befehle einbezogen wird, die an die Datenbank gesendet werden. Das Attribut wird "Timestamp" genannt, da in früheren Versionen von SQL Server ein SQL-Zeitstempeldatentyp verwendet wurde, bevor die SQL-Zeilenversion ihn ersetzt hat. Der .Net-Typ für rowversion ist ein Bytearray. Wenn Sie die Fluent-API verwenden möchten, können Sie die IsConcurrencyToken-Methode verwenden, um die Tracking-Eigenschaft anzugeben, wie im folgenden Beispiel gezeigt:

modelBuilder.Entity<Department>()
    .Property(p => p.RowVersion).IsConcurrencyToken();

Siehe das GitHub-Problem Replace IsConcurrencyToken durch IsRowVersion.

Durch das Hinzufügen einer Eigenschaft ändern Sie das Datenbankmodell, daher müssen Sie eine weitere Migration durchführen. Geben Sie die folgenden Befehle in die Paket-Manager-Konsole ein:

Add-Migration RowVersion
Update-Database

Erstellen eines Abteilungscontrollers

Erstellen Sie einen Department Controller und zeigt auf die gleiche Weise wie die anderen Controller an, indem Sie die folgenden Einstellungen verwenden:

Add_Controller_dialog_box_for_Department_controller

Fügen Sie in Controller\DepartmentController.cs eine using Anweisung hinzu:

using System.Data.Entity.Infrastructure;

Ändern Sie "LastName" überall in dieser Datei (vier Vorkommen), sodass die Dropdownlisten des Abteilungsadministrators den vollständigen Namen des Kursleiters und nicht nur den Nachnamen enthalten.

ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName");

Ersetzen Sie den vorhandenen Code für die HttpPost Edit Methode durch den folgenden Code:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "DepartmentID, Name, Budget, StartDate, RowVersion, InstructorID")]
    Department department)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(department).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DbUpdateConcurrencyException ex)
   {
      var entry = ex.Entries.Single();
      var clientValues = (Department)entry.Entity;
      var databaseValues = (Department)entry.GetDatabaseValues().ToObject();

      if (databaseValues.Name != clientValues.Name)
         ModelState.AddModelError("Name", "Current value: "
             + databaseValues.Name);
      if (databaseValues.Budget != clientValues.Budget)
         ModelState.AddModelError("Budget", "Current value: "
             + String.Format("{0:c}", databaseValues.Budget));
      if (databaseValues.StartDate != clientValues.StartDate)
         ModelState.AddModelError("StartDate", "Current value: "
             + String.Format("{0:d}", databaseValues.StartDate));
      if (databaseValues.InstructorID != clientValues.InstructorID)
         ModelState.AddModelError("InstructorID", "Current value: "
             + db.Instructors.Find(databaseValues.InstructorID).FullName);
      ModelState.AddModelError(string.Empty, "The record you attempted to edit "
          + "was modified by another user after you got the original value. The "
          + "edit operation was canceled and the current values in the database "
          + "have been displayed. If you still want to edit this record, click "
          + "the Save button again. Otherwise click the Back to List hyperlink.");
      department.RowVersion = databaseValues.RowVersion;
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
      ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
   }

   ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName", department.InstructorID);
   return View(department);
}

Die Ansicht speichert den ursprünglichen RowVersion Wert in einem ausgeblendeten Feld. Wenn der Modellordner die department Instanz erstellt, weist dieses Objekt den ursprünglichen RowVersion Eigenschaftswert und die neuen Werte für die anderen Eigenschaften auf, wie vom Benutzer auf der Seite "Bearbeiten" eingegeben. Wenn das Entity Framework einen SQL-Befehl UPDATE erstellt, enthält dieser Befehl eine WHERE Klausel, die nach einer Zeile mit dem ursprünglichen RowVersion Wert sucht.

Wenn keine Zeilen vom UPDATE Befehl betroffen sind (keine Zeilen haben den ursprünglichen RowVersion Wert), löst Das Entity Framework eine DbUpdateConcurrencyException Ausnahme aus, und der Code im catch Block ruft die betroffene Department Entität aus dem Ausnahmeobjekt ab. Diese Entität enthält sowohl die Werte, die aus der Datenbank gelesen werden, als auch die neuen Werte, die vom Benutzer eingegeben wurden:

var entry = ex.Entries.Single();
var clientValues = (Department)entry.Entity;
var databaseValues = (Department)entry.GetDatabaseValues().ToObject();

Als Nächstes fügt der Code eine benutzerdefinierte Fehlermeldung für jede Spalte hinzu, die Datenbankwerte aufweist, die sich von dem, was der Benutzer auf der Seite "Bearbeiten" eingegeben hat, unterscheiden:

if (databaseValues.Name != currentValues.Name)
    ModelState.AddModelError("Name", "Current value: " + databaseValues.Name);
    // ...

Eine längere Fehlermeldung erklärt, was passiert ist und was sie tun müssen:

ModelState.AddModelError(string.Empty, "The record you attempted to edit "
    + "was modified by another user after you got the original value. The"
    + "edit operation was canceled and the current values in the database "
    + "have been displayed. If you still want to edit this record, click "
    + "the Save button again. Otherwise click the Back to List hyperlink.");

Schließlich legt der Code den RowVersion Wert des Department Objekts auf den neuen Wert fest, der aus der Datenbank abgerufen wird. Dieser neue RowVersion-Wert wird in dem ausgeblendeten Feld gespeichert, wenn die Seite „Bearbeiten“ erneut angezeigt wird. Das nächste Mal, wenn der Benutzer auf Speichern klickt, werden nur Parallelitätsfehler abgefangen, die nach dem erneuten Anzeigen der Seite „Bearbeiten“ aufgetreten sind.

Fügen Sie in Views\Department\Edit.cshtml ein ausgeblendetes Feld hinzu, um den RowVersion Eigenschaftswert zu speichern, unmittelbar nach dem ausgeblendeten Feld für die DepartmentID Eigenschaft:

@model ContosoUniversity.Models.Department

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Department</legend>

        @Html.HiddenFor(model => model.DepartmentID)
        @Html.HiddenFor(model => model.RowVersion)

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>

Ersetzen Sie in Views\Department\Index.cshtml den vorhandenen Code durch den folgenden Code, um Zeilenlinks nach links zu verschieben, und ändern Sie den Seitentitel und die Spaltenüberschriften so, dass sie anstelle LastName der Spalte "Administrator" angezeigt werdenFullName:

@model IEnumerable<ContosoUniversity.Models.Department>

@{
    ViewBag.Title = "Departments";
}

<h2>Departments</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>Name</th>
        <th>Budget</th>
        <th>Start Date</th>
        <th>Administrator</th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) |
            @Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID })
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Budget)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.StartDate)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Administrator.FullName)
        </td>
    </tr>
}

</table>

Testen der optimistischen Parallelitätsbehandlung

Führen Sie die Website aus, und klicken Sie auf "Abteilungen":

Screenshot der Seite

Klicken Sie mit der rechten Maustaste auf den Link "Bearbeiten " für Kim Abercrombie, und wählen Sie "In neuer Registerkarte öffnen" aus, und klicken Sie dann auf den Link "Bearbeiten " für Kim Abercrombie. In den beiden Fenstern werden dieselben Informationen angezeigt.

Department_Edit_page_before_changes

Ändern Sie ein Feld im ersten Browserfenster, und klicken Sie auf " Speichern".

Department_Edit_page_1_after_change

Der Browser zeigt die Indexseite mit dem geänderten Wert an.

Departments_Index_page_after_first_budget_edit

Ändern Sie das beliebige Feld im zweiten Browserfenster, und klicken Sie auf " Speichern".

Department_Edit_page_2_after_change

Klicken Sie im zweiten Browserfenster auf " Speichern ". Folgende Fehlermeldung wird angezeigt:

Screenshot der Seite

Klicken Sie erneut auf Speichern. Der wert, den Sie im zweiten Browser eingegeben haben, wird zusammen mit dem ursprünglichen Wert der Daten gespeichert, die Sie im ersten Browser ändern. Die gespeicherten Werte werden Ihnen auf der Indexseite angezeigt.

Department_Index_page_with_change_from_second_browser

Aktualisieren der Seite "Löschen"

Bei der Seite „Löschen“ entdeckt Entity Framework Nebenläufigkeitskonflikte, die durch die Bearbeitung einer Abteilung ausgelöst wurden, auf ähnliche Weise. Wenn die HttpGet Delete Methode die Bestätigungsansicht anzeigt, enthält die Ansicht den ursprünglichen RowVersion Wert in einem ausgeblendeten Feld. Dieser Wert ist dann für die Methode verfügbar, die HttpPost Delete aufgerufen wird, wenn der Benutzer den Löschvorgang bestätigt. Wenn das Entity Framework den SQL-Befehl DELETE erstellt, enthält es eine WHERE Klausel mit dem ursprünglichen RowVersion Wert. Wenn der Befehl zu null Zeilen führt, die betroffen sind (d. h., die Zeile wurde geändert, nachdem die Bestätigungsseite "Löschen" angezeigt wurde), wird eine Parallelitätsausnahme ausgelöst, und die HttpGet Delete Methode wird mit einem Fehlerkennzeichnung aufgerufen, das true festgelegt ist, um die Bestätigungsseite mit einer Fehlermeldung erneut anzuzeigen. Es ist auch möglich, dass null Zeilen betroffen waren, da die Zeile von einem anderen Benutzer gelöscht wurde, sodass in diesem Fall eine andere Fehlermeldung angezeigt wird.

Ersetzen Sie in DepartmentController.cs die HttpGet Delete Methode durch den folgenden Code:

public ActionResult Delete(int id, bool? concurrencyError)
{
    Department department = db.Departments.Find(id);

    if (concurrencyError.GetValueOrDefault())
    {
        if (department == null)
        {
            ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
                + "was deleted by another user after you got the original values. "
                + "Click the Back to List hyperlink.";
        }
        else
        {
            ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
                + "was modified by another user after you got the original values. "
                + "The delete operation was canceled and the current values in the "
                + "database have been displayed. If you still want to delete this "
                + "record, click the Delete button again. Otherwise "
                + "click the Back to List hyperlink.";
        }
    }

    return View(department);
}

Die Methode akzeptiert einen optionalen Parameter, der angibt, ob die Seite nach einem Parallelitätsfehler erneut angezeigt wird. Wenn dieses Kennzeichen lautet true, wird eine Fehlermeldung mithilfe einer ViewBag Eigenschaft an die Ansicht gesendet.

Ersetzen Sie den Code in der HttpPost Delete Methode (benannt DeleteConfirmed) durch den folgenden Code:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(Department department)
{
    try
    {
        db.Entry(department).State = EntityState.Deleted;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    catch (DbUpdateConcurrencyException)
    {
        return RedirectToAction("Delete", new { concurrencyError=true } );
    }
    catch (DataException /* dex */)
    {
        //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
        ModelState.AddModelError(string.Empty, "Unable to delete. Try again, and if the problem persists contact your system administrator.");
        return View(department);
    }
}

In dem eingerüsteten Code, den Sie soeben ersetzt haben, akzeptiert diese Methode nur eine Datensatz-ID:

public ActionResult DeleteConfirmed(int id)

Diesen Parameter haben Sie in eine Department-Entitätsinstanz geändert, die durch die Modellbindung erstellt wurde. Dadurch erhalten Sie zusätzlich zum Datensatzschlüssel Zugriff auf den RowVersion Eigenschaftswert.

public ActionResult Delete(Department department)

Ebenfalls haben Sie den Namen der Aktionsmethode DeleteConfirmed auf Delete geändert. Der Gerüstcode namens der HttpPost Delete Methode, um der HttpPost Methode DeleteConfirmed eine eindeutige Signatur zu geben. (Die CLR erfordert überladene Methoden, um unterschiedliche Methodenparameter zu haben.) Nachdem die Signaturen eindeutig sind, können Sie mit der MVC-Konvention bleiben und denselben Namen für die HttpPost Methoden und HttpGet Löschmethoden verwenden.

Wenn ein Parallelitätsfehler abgefangen wird, zeigt der Code erneut die Bestätigungsseite „Löschen“ an, und stellt ein Flag bereit, das angibt, dass eine Fehlermeldung für die Parallelität angezeigt werden soll.

Ersetzen Sie in Views\Department\Delete.cshtml den Gerüstcode durch den folgenden Code, der Formatierungsänderungen vorgibt, und fügt ein Fehlermeldungsfeld hinzu. Die Änderungen werden hervorgehoben.

@model ContosoUniversity.Models.Department

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<p class="error">@ViewBag.ConcurrencyErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<fieldset>
    <legend>Department</legend>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Name)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Budget)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Budget)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.StartDate)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.StartDate)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Administrator.FullName)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Administrator.FullName)
    </div>
</fieldset>
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
   @Html.HiddenFor(model => model.DepartmentID)
    @Html.HiddenFor(model => model.RowVersion)
    <p>
        <input type="submit" value="Delete" /> |
        @Html.ActionLink("Back to List", "Index")
    </p>
}

Dieser Code fügt eine Fehlermeldung zwischen den Überschriften und h3 den h2 Überschriften hinzu:

<p class="error">@ViewBag.ConcurrencyErrorMessage</p>

Er wird durch FullName das Administrator Feld ersetztLastName:

<div class="display-label">
    @Html.LabelFor(model => model.InstructorID)
</div>
<div class="display-field">
    @Html.DisplayFor(model => model.Administrator.FullName)
</div>

Schließlich werden ausgeblendete Felder für die DepartmentID und RowVersion Eigenschaften nach der Html.BeginForm Anweisung hinzugefügt:

@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)

Führen Sie die Seite "Abteilungsindex" aus. Klicken Sie mit der rechten Maustaste auf den Link "Löschen " für die englische Abteilung, und wählen Sie "In neuem Fenster öffnen" aus, und klicken Sie dann im ersten Fenster auf den Link "Bearbeiten" für die englische Abteilung.

Ändern Sie im ersten Fenster einen der Werte, und klicken Sie auf Speichern:

Department_Edit_page_after_change_before_delete

Die Indexseite bestätigt die Änderung.

Departments_Index_page_after_budget_edit_before_delete

Klicken Sie im zweiten Fenster auf "Löschen".

Department_Delete_confirmation_page_before_concurrency_error

Ihnen wird eine Fehlermeldung zur Parallelität angezeigt, und die Abteilungswerte werden mit den aktuellen Werten der Datenbank aktualisiert.

Department_Delete_confirmation_page_with_concurrency_error

Wenn Sie erneut auf Löschen klicken, werden Sie auf die Indexseite weitergeleitet, die anzeigt, dass die Abteilung gelöscht wurde.

Zusammenfassung

Damit ist die Einführung in die Behandlung von Nebenläufigkeitskonflikten abgeschlossen. Informationen zu anderen Möglichkeiten zur Behandlung verschiedener Parallelitätsszenarien finden Sie im Blog "Optimistische Parallelitätsmuster " und "Arbeiten mit Eigenschaftswerten " im Teamblog "Entity Framework". Das nächste Lernprogramm zeigt, wie Sie die Vererbung pro Hierarchie für die Instructor Und-Entitäten Student implementieren.

Links zu anderen Entity Framework-Ressourcen finden Sie in der ASP.NET Datenzugriffs-Inhaltszuordnung.