Freigeben über


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

In früheren Lernprogrammen haben Sie gelernt, wie Sie Daten aktualisieren. In diesem Lernprogramm 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 der Seite

Screenshot der Seite

In diesem Tutorial:

  • Erhalten Sie Informationen über Parallelitätskonflikte
  • Optimistische Parallelität hinzufügen
  • Abteilungscontroller ändern
  • Testen der Parallelitätsbehandlung
  • 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. 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 $.

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

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

Optimistische Parallelität hinzufügen

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)]
    [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 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();

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

Abteilungscontroller ändern

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

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 Kursleiters und nicht nur den Nachnamen enthalten.

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

Ersetzen Sie den vorhandenen Code für die HttpPost Edit 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 angezeigte Code verwendet die geposteten Formularwerte, um eine Abteilungsentität zu erstellen, damit 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 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.

var entry = ex.Entries.Single();

Dieses Objekt hat die neuen Werte, die der Benutzer in seiner Entity Eigenschaft eingegeben hat, und Sie können die Werte abrufen, die aus der Datenbank gelesen werden, 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 den Löschvorgang überprüft haben, wäre dies nur null, databaseEntry wenn die Abteilung nach FindAsync ausführung und vor SaveChanges Ausführung 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, 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()
    
    <div class="form-horizontal">
        <h4>Department</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.DepartmentID)
        @Html.HiddenFor(model => model.RowVersion)

Testen der Parallelitätsbehandlung

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 dieselben 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 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 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 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 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 Datensatzschlüssel Zugriff auf den RowVersion Eigenschaftswert.

public async Task<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 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 Überschriften und h3 den h2 Überschriften hinzu:

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

Er wird durch FullName das Administrator Feld ersetztLastName:

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

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 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 Indexseite 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. Das nächste Lernprogramm zeigt, wie Sie die Vererbung pro Hierarchie für die Instructor Und-Entitäten 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

Wechseln Sie zum nächsten Artikel, um zu erfahren, wie Sie die Vererbung im Datenmodell implementieren.