Tutorial: Behandeln der Parallelität mit EF in einer ASP.NET MVC 5-App

In früheren Tutorials haben Sie gelernt, wie Sie Daten aktualisieren. In diesem Tutorial wird gezeigt, wie Sie optimistische Parallelität verwenden, um Konflikte zu behandeln, wenn mehrere Benutzer die gleiche Entität gleichzeitig aktualisieren. Sie ändern die Webseiten, die mit der Department Entität arbeiten, sodass sie Parallelitätsfehler behandeln. In der nachfolgenden Abbildung sehen Sie die Seiten „Bearbeiten“ und „Löschen“, einschließlich einiger Meldungen, die angezeigt werden, wenn ein Parallelitätskonflikt auftritt.

Screenshot: Seite

Screenshot: Seite

In diesem Tutorial:

  • Erhalten Sie Informationen über Parallelitätskonflikte
  • Hinzufügen von optimistischer Parallelität
  • Ändern des Abteilungscontrollers
  • Parallelitätsbehandlung für Tests
  • Aktualisieren der Seite „Delete“ (Löschen)

Voraussetzungen

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. Dies erfordert erhebliche Datenbankverwaltungsressourcen und kann mit steigender Anzahl der Benutzer einer Anwendung zu Leistungsproblemen führen. 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 die 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 "Departments Edit" aus und ändert den Budgetbetrag für die englische Abteilung von $350.000,00 in $0,00.

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

John klickt zuerst auf Speichern und sieht seine Änderung, wenn der Browser zur Indexseite zurückkehrt, 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 Johns als auch Janes Änderungen angezeigt – ein Startdatum am 8.08.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. Das Verwalten vieler Zuständen kann sich auf die Leistung der Anwendung auswirken, da es entweder Serverressourcen beansprucht oder in der Webseite selbst (z.B. in ausgeblendeten Feldern) oder in einem Cookie enthalten sein muss.

  • Sie können Janes Änderung überschreiben Lassen Sie Johns Änderung. Wenn jemand das nächste Mal die englische Abteilung durchsucht, wird der Wert vom 8.8.2013 und der wiederhergestellte Wert von $350.000,00 angezeigt. Das ist entweder ein Client gewinnt- oder ein Letzter Schreiber gewinnt-Szenario. (Alle Werte des Clients haben Vorrang vor dem Datenspeicher.) Wie in der Einführung dieses Abschnitts beschrieben, geschieht dies automatisch, wenn Sie für die Behandlung der Parallelität keinen Code schreiben.

  • 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 zu übernehmen, wenn sie sie noch 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 Vom Entity Framework ausgelöste Ausnahmen vom Typ OptimisticConcurrencyException behandeln. 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 sequenzielle Zahl, die bei jeder Aktualisierung der Zeile inkrementiert wird. In einem Update - oder Delete -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 vom ursprünglichen Wert, sodass die Update - oder Delete -Anweisung die zu aktualisierende Zeile aufgrund der Where -Klausel nicht finden kann. Wenn Entity Framework feststellt, dass keine Zeilen vom Update Befehl oder Delete aktualisiert wurden (d. h. wenn die Anzahl der betroffenen Zeilen null ist), interpretiert es dies als Parallelitätskonflikt.

  • Konfigurieren Sie das Entity Framework, um die ursprünglichen Werte jeder Spalte in der Tabelle in den -Klauseln von Update und Delete -WhereBefehlen einzuschließen.

    Wie bei der ersten Option gibt die -Klausel, wenn sich etwas in der Zeile seit dem ersten Lesen der Zeile geändert hat, Where keine zu aktualisierende Zeile zurück, die vom Entity Framework als Parallelitätskonflikt interpretiert wird. Bei Datenbanktabellen, die viele Spalten enthalten, kann dieser Ansatz zu sehr großen Where Klauseln führen und erfordern, dass Sie große Zustandsmengen beibehalten. Wie bereits erwähnt, kann das Verwalten großer Mengen von Zuständen die Anwendungsleistung beeinträchtigen. Deshalb wird dieser Ansatz in der Regel nicht empfohlen, und ist nicht die Methode, die in diesem Tutorial verwendet wird.

    Wenn Sie diesen Parallelitätsansatz 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 ihnen 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 weiteren Verlauf dieses Tutorials fügen Sie der Department Entität eine Rowversion-Nachverfolgungseigenschaft hinzu, erstellen einen Controller und Ansichten und testen, ob alles ordnungsgemäß funktioniert.

Hinzufügen von optimistischer Parallelität

Fügen Sie in Models\Department.cs eine Nachverfolgungseigenschaft 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)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Start 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 An die Where Datenbank gesendete -Klausel von Update - und Delete -Befehlen eingeschlossen wird. Das Attribut wird als Timestamp bezeichnet, da frühere Versionen von SQL Server einen SQL-Zeitstempeldatentyp verwendet haben, bevor die SQL-Rowversion ihn ersetzt hat. Der .NET-Typ für rowversion ist ein Bytearray.

Wenn Sie lieber die Fluent-API verwenden möchten, können Sie die IsConcurrencyToken-Methode verwenden, um die Nachverfolgungseigenschaft anzugeben, wie im folgenden Beispiel gezeigt:

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

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

Ändern des Abteilungscontrollers

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

using System.Data.Entity.Infrastructure;

Ändern Sie in der Datei DepartmentController.cs alle vier Vorkommen von "LastName" in "FullName", sodass die Dropdownlisten des Abteilungsadministrators den vollständigen Namen des Dozenten und nicht nur den Nachnamen enthalten.

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

Ersetzen Sie den vorhandenen Code für die HttpPostEdit -Methode durch den folgenden Code:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int? id, byte[] rowVersion)
{
    string[] fieldsToBind = new string[] { "Name", "Budget", "StartDate", "InstructorID", "RowVersion" };

    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    var departmentToUpdate = await db.Departments.FindAsync(id);
    if (departmentToUpdate == null)
    {
        Department deletedDepartment = new Department();
        TryUpdateModel(deletedDepartment, fieldsToBind);
        ModelState.AddModelError(string.Empty,
            "Unable to save changes. The department was deleted by another user.");
        ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", deletedDepartment.InstructorID);
        return View(deletedDepartment);
    }

    if (TryUpdateModel(departmentToUpdate, fieldsToBind))
    {
        try
        {
            db.Entry(departmentToUpdate).OriginalValues["RowVersion"] = rowVersion;
            await db.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();
            var clientValues = (Department)entry.Entity;
            var databaseEntry = entry.GetDatabaseValues();
            if (databaseEntry == null)
            {
                ModelState.AddModelError(string.Empty,
                    "Unable to save changes. The department was deleted by another user.");
            }
            else
            {
                var databaseValues = (Department)databaseEntry.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.");
                departmentToUpdate.RowVersion = databaseValues.RowVersion;
            }
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", departmentToUpdate.InstructorID);
    return View(departmentToUpdate);
}

Wenn die Methode FindAsync NULL zurückgibt, wurde die Abteilung von einem anderen Benutzer gelöscht. Der gezeigte Code verwendet die bereitgestellten Formularwerte, um eine Abteilungsentität zu erstellen, sodass die Seite Bearbeiten mit einer Fehlermeldung erneut angezeigt werden kann. Alternativ müssen Sie die Abteilungsentität nicht erneut erstellen, wenn Sie nur eine Fehlermeldung anzeigen, ohne die Abteilungsfelder erneut anzuzeigen.

Die Ansicht speichert den ursprünglichen RowVersion Wert in einem ausgeblendeten Feld, und die -Methode empfängt ihn im rowVersion -Parameter. Bevor Sie SaveChanges aufrufen, müssen Sie diesen ursprünglichen Eigenschaftswert von RowVersion in die Auflistung OriginalValues für die Entität einfügen. Wenn das Entity Framework dann einen SQL-Befehl UPDATE erstellt, enthält dieser Befehl eine WHERE -Klausel, die nach einer Zeile sucht, die den ursprünglichen RowVersion Wert aufweist.

Wenn vom Befehl keine Zeilen betroffen UPDATE sind (keine Zeilen haben den ursprünglichen RowVersion Wert), löst entity Framework eine Ausnahme aus DbUpdateConcurrencyException , und der Code im catch Block ruft die betroffene Department Entität aus dem Ausnahmeobjekt ab.

var entry = ex.Entries.Single();

Dieses Objekt enthält die neuen Werte, die der Benutzer in seiner Entity -Eigenschaft eingegeben hat, und Sie können die Werte aus der Datenbank lesen, indem Sie die GetDatabaseValues -Methode aufrufen.

var clientValues = (Department)entry.Entity;
var databaseEntry = entry.GetDatabaseValues();

Die GetDatabaseValues -Methode gibt NULL zurück, wenn jemand die Zeile aus der Datenbank gelöscht hat. Andernfalls müssen Sie das zurückgegebene Objekt in die Department -Klasse umwandeln, um auf die Department Eigenschaften zuzugreifen. (Da Sie bereits auf Löschung überprüft haben, wäre nur null, databaseEntry wenn die Abteilung nach FindAsync den Ausführungen und vor den SaveChanges Ausführungen gelöscht wurde.)

if (databaseEntry == null)
{
    ModelState.AddModelError(string.Empty,
        "Unable to save changes. The department was deleted by another user.");
}
else
{
    var databaseValues = (Department)databaseEntry.ToObject();

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

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

In einer längeren Fehlermeldung wird erläutert, was passiert ist und was zu tun ist:

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()
    
    <div class="form-horizontal">
        <h4>Department</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.DepartmentID)
        @Html.HiddenFor(model => model.RowVersion)

Parallelitätsbehandlung für Tests

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

Klicken Sie mit der rechten Maustaste auf den Link Bearbeiten für die englische Abteilung, und wählen Sie In neuer Registerkarte öffnen aus, und klicken Sie dann auf den Link Bearbeiten für die englische Abteilung. Auf den beiden Registerkarten werden die gleichen Informationen angezeigt.

Ändern Sie ein Feld in der ersten Registerkarte, und klicken Sie auf Speichern.

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

Ändern Sie ein Feld auf der zweiten Browserregisterkarte, und klicken Sie auf Speichern. Folgende Fehlermeldung wird angezeigt:

Screenshot der Seite

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

Aktualisieren der Seite „Delete“ (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 HttpGetDelete -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 HttpPostDelete aufgerufen wird, wenn der Benutzer den Löschvorgang bestätigt. Wenn entity Framework den SQL-Befehl DELETE erstellt, enthält es eine WHERE -Klausel mit dem ursprünglichen RowVersion Wert. Wenn der Befehl zu 0 betroffenen Zeilen führt (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 Fehlerflag aufgerufen, das auf true festgelegt ist, um die Bestätigungsseite erneut mit einer Fehlermeldung 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 HttpGetDelete -Methode durch den folgenden Code:

public async Task<ActionResult> Delete(int? id, bool? concurrencyError)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Department department = await db.Departments.FindAsync(id);
    if (department == null)
    {
        if (concurrencyError.GetValueOrDefault())
        {
            return RedirectToAction("Index");
        }
        return HttpNotFound();
    }

    if (concurrencyError.GetValueOrDefault())
    {
        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 Flag lautet true, wird eine Fehlermeldung mithilfe einer ViewBag -Eigenschaft an die Ansicht gesendet.

Ersetzen Sie den Code in der HttpPostDelete -Methode (mit dem Namen DeleteConfirmed) durch den folgenden Code:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Delete(Department department)
{
    try
    {
        db.Entry(department).State = EntityState.Deleted;
        await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    catch (DbUpdateConcurrencyException)
    {
        return RedirectToAction("Delete", new { concurrencyError = true, id=department.DepartmentID });
    }
    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 async Task<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 RowVersion Datensatzschlüssel Zugriff auf den Eigenschaftswert.

public async Task<ActionResult> Delete(Department department)

Ebenfalls haben Sie den Namen der Aktionsmethode DeleteConfirmed auf Delete geändert. Der gerüstete Code namens die HttpPostDelete -Methode DeleteConfirmed , um der HttpPost Methode eine eindeutige Signatur zu geben. ( Die CLR erfordert überladene Methoden, um unterschiedliche Methodenparameter zu haben.) Nachdem die Signaturen eindeutig sind, können Sie sich an die MVC-Konvention halten und denselben Namen für die HttpPost Methoden und HttpGet löschen 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üsteten Code durch den folgenden Code, der ein Fehlermeldungsfeld und ausgeblendete Felder für die Eigenschaften DepartmentID und RowVersion hinzufügt. 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>
<div>
    <h4>Department</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            Administrator
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Administrator.FullName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.Budget)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Budget)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.StartDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.StartDate)
        </dd>

    </dl>

    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
        @Html.HiddenFor(model => model.DepartmentID)
        @Html.HiddenFor(model => model.RowVersion)

        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div>

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

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

Im Feld wird durch FullNameAdministrator ersetztLastName:

<dt>
  Administrator
</dt>
<dd>
  @Html.DisplayFor(model => model.Administrator.FullName)
</dd>

Schließlich werden ausgeblendete Felder für die DepartmentID Eigenschaften und RowVersion 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 neuer Registerkarte öffnen aus, und klicken Sie dann auf der ersten Registerkarte auf den Link Bearbeiten für die englische Abteilung.

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

Die Seite Index bestätigt die Änderung.

Klicken Sie in der zweiten Registerkarte auf Löschen.

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.

Abrufen des Codes

Abgeschlossenes Projekt herunterladen

Zusätzliche Ressourcen

Links zu anderen Entity Framework-Ressourcen finden Sie im ASP.NET Datenzugriff – Empfohlene Ressourcen.

Informationen zu anderen Möglichkeiten zum Behandeln verschiedener Parallelitätsszenarien finden Sie unter Optimistische Parallelitätsmuster und Arbeiten mit Eigenschaftswerten auf MSDN. Im nächsten Tutorial wird gezeigt, wie Sie die Vererbung von Tabellen pro Hierarchie für die Instructor Entitäten und Student implementieren.

Nächste Schritte

In diesem Tutorial:

  • Haben Sie Informationen über Parallelitätskonflikte erhalten
  • Optimistische Parallelität hinzugefügt
  • Geänderter Abteilungscontroller
  • Getestete Parallelitätsbehandlung
  • Die Seite „Löschen“ aktualisiert

Fahren Sie mit dem nächsten Artikel fort, um zu erfahren, wie Sie die Vererbung im Datenmodell implementieren.