Samouczek: obsługa współbieżności z EF w aplikacji ASP.NET MVC 5

We wcześniejszych samouczkach przedstawiono sposób aktualizowania danych. W tym samouczku pokazano, jak używać optymistycznej współbieżności, aby obsłużyć konflikty, gdy wielu użytkowników jednocześnie zaktualizuje tę samą jednostkę. Można zmienić strony sieci Web, które współpracują z jednostką Department, aby obsługiwały błędy współbieżności. Na poniższych ilustracjach przedstawiono strony edycji i usuwania, w tym niektóre komunikaty, które są wyświetlane w przypadku wystąpienia konfliktu współbieżności.

Department_Edit_page_2_after_clicking_Save

Department_Edit_page_2_after_clicking_Save

W tym samouczku zostaną wykonane następujące czynności:

  • Informacje o konfliktach współbieżności
  • Dodawanie optymistycznej współbieżności
  • Modyfikowanie kontrolera działu
  • Obsługa współbieżności testów
  • Aktualizuj stronę Delete

Wymagania wstępne

Konfliktów współbieżności

Konflikt współbieżności występuje, gdy jeden użytkownik wyświetla dane jednostki w celu ich edycji, a następnie inny użytkownik aktualizuje dane tej samej jednostki przed zapisaniem w bazie danych pierwszego użytkownika. Jeśli nie włączysz wykrywania takich konfliktów, osoba, która aktualizuje bazę danych, ostatnio zastępuje zmiany wprowadzone przez innych użytkowników. W wielu aplikacjach to ryzyko jest akceptowalne: w przypadku kilku użytkowników lub kilku aktualizacji lub jeśli nie jest to naprawdę krytyczne, jeśli niektóre zmiany zostaną nadpisywane, koszt programowania współbieżności może wznieść korzyści. W takim przypadku nie trzeba konfigurować aplikacji do obsługi konfliktów współbieżności.

Współbieżność pesymistyczna (blokowanie)

Jeśli aplikacja musi zapobiegać przypadkowej utracie danych w scenariuszach współbieżności, jeden ze sposobów jest używany do blokowania baz danych. Jest to nazywane pesymistyczną współbieżnością. Na przykład przed odczytaniem wiersza z bazy danych należy zażądać blokady dla dostępu tylko do odczytu lub do aktualizacji. Jeśli zablokujesz wiersz na potrzeby dostępu do aktualizacji, żaden inny użytkownik nie będzie mógł zablokować wiersza dla dostępu tylko do odczytu lub aktualizacji, ponieważ spowodowałoby to skopiowanie danych w procesie. Jeśli zablokujesz wiersz dla dostępu tylko do odczytu, inne osoby mogą także zablokować dostęp tylko do odczytu, ale nie dla aktualizacji.

Zarządzanie blokadami ma wady. Może być skomplikowany dla programu. Wymaga to znaczących zasobów zarządzania bazami danych. może to spowodować problemy z wydajnością w miarę zwiększania się liczby użytkowników aplikacji. Z tego względu nie wszystkie systemy zarządzania bazami danych obsługują pesymistyczne współbieżności. Entity Framework nie zapewnia wbudowanej pomocy technicznej i ten samouczek nie pokazuje, jak go wdrożyć.

Optymistyczna współbieżność

Alternatywą dla pesymistycznej współbieżności jest Optymistyczna współbieżność. Współbieżność optymistyczna pozwala na wykonywanie konfliktów współbieżności, a następnie podejmowanie odpowiednich działań. Na przykład Jan uruchamia stronę Edytowanie działów, zmienia kwotę budżetu dla działu angielskiego z $350 000,00 na $0,00.

Przed kliknięciem przycisku Zapisz, Janina uruchamia tę samą stronę i zmienia wartość pola data rozpoczęcia z 9/1/2007 na 8/8/2013.

Jan klika pozycję Zapisz jako pierwszy i widzi zmiany, gdy przeglądarka powróci do strony indeks, a następnie kliknij przycisk Zapisz. Co dzieje się potem określają sposób obsługi konfliktów współbieżności. Dostępne są następujące opcje:

  • Można śledzić, która właściwość została zmodyfikowana przez użytkownika i zaktualizować tylko odpowiednie kolumny w bazie danych. W przykładowym scenariuszu żadne dane nie zostaną utracone, ponieważ różne właściwości zostały zaktualizowane przez dwóch użytkowników. Następnym razem, gdy ktoś przegląda dział w języku angielskim, zobaczy zmiany w wysokości Jan i Janina — datę początkową 8/8/2013 i budżet zerowych dolarów.

    Ta metoda aktualizacji może zmniejszyć liczbę konfliktów, które mogłyby spowodować utratę danych, ale nie może uniknąć utraty danych, jeśli wprowadzono konkurencyjne zmiany w tej samej właściwości jednostki. Czy Entity Framework działa w ten sposób, zależy od sposobu implementacji kodu aktualizacji. Często nie jest to praktyczne w aplikacji sieci Web, ponieważ może wymagać utrzymania dużej ilości danych w celu śledzenia wszystkich oryginalnych wartości właściwości dla jednostki, a także nowych wartości. Obsługa dużych ilości Stanów może wpłynąć na wydajność aplikacji, ponieważ wymaga zasobów serwera lub musi być uwzględniona na stronie sieci Web (na przykład w ukrytych polach) lub w pliku cookie.

  • Możesz pozwolić, aby zmiana została zastąpiona przez Jan. Następnym razem, gdy ktoś przegląda dział w języku angielskim, zobaczy 8/8/2013 i przywrócona wartość $350 000,00. Jest to tzw. klient WINS lub ostatni w scenariuszu usługi WINS . (Wszystkie wartości z klienta mają pierwszeństwo przed tym, co znajduje się w magazynie danych). Jak zostało to opisane we wprowadzeniu do tej sekcji, jeśli nie wykonasz kodowania na potrzeby obsługi współbieżności, zostanie to wykonane automatycznie.

  • Można zapobiec aktualizacji firmy Janina ze zmian w bazie danych. Zwykle zostanie wyświetlony komunikat o błędzie, pokazany bieżący stan danych i umożliwi to ponowne zastosowanie jego zmian, jeśli nadal chce je wprowadzić. Jest to tzw. scenariusz magazynu usługi WINS . (Wartości ze sklepu danych mają pierwszeństwo przed wartościami przesyłanymi przez klienta). W tym samouczku zostanie wdrożony scenariusz sklepu WINS. Ta metoda zapewnia, że żadne zmiany nie są zastępowane bez alertu użytkownika o tym, co się dzieje.

Wykrywanie konfliktów współbieżności

Konflikty można rozwiązać przez obsługę wyjątków OptimisticConcurrencyException zgłaszanych przez Entity Framework. Aby dowiedzieć się, kiedy należy zgłosić te wyjątki, Entity Framework musi mieć możliwość wykrywania konfliktów. W związku z tym należy odpowiednio skonfigurować bazę danych i model danych. Dostępne są następujące opcje włączania wykrywania konfliktów:

  • W tabeli bazy danych Dołącz kolumnę śledzenia, której można użyć do określenia, kiedy wiersz został zmieniony. Następnie można skonfigurować Entity Framework, aby uwzględnić tę kolumnę w klauzuli Where Update SQL lub Delete polecenia.

    Typ danych kolumny śledzenia jest zazwyczaj rowversion. Wartość rowversion jest kolejnym numerem, który jest zwiększany za każdym razem, gdy wiersz zostanie zaktualizowany. W Update lub Delete, klauzula Where zawiera oryginalną wartość kolumny śledzenia (oryginalną wersję wiersza). Jeśli aktualizowany wiersz został zmieniony przez innego użytkownika, wartość w kolumnie rowversion różni się od oryginalnej wartości, dlatego instrukcja Update lub Delete nie może znaleźć wiersza do zaktualizowania z powodu klauzuli Where. Gdy Entity Framework stwierdzi, że żadne wiersze nie zostały zaktualizowane przez polecenie Update lub Delete (oznacza to, że gdy liczba odnośnych wierszy wynosi zero), interpretuje to jako konflikt współbieżności.

  • Skonfiguruj Entity Framework, aby uwzględnić oryginalne wartości każdej kolumny w tabeli w klauzuli Where poleceń Update i Delete.

    Jak w pierwszej opcji, jeśli coś w wierszu uległo zmianie od momentu pierwszego odczytu wiersza, klauzula Where nie zwróci wiersza do zaktualizowania, który Entity Framework interpretuje jako konflikt współbieżności. W przypadku tabel bazy danych z wieloma kolumnami takie podejście może powodować bardzo duże Where klauzule i może wymagać utrzymania dużej ilości danych. Jak wspomniano wcześniej, utrzymanie dużej ilości stanu może wpłynąć na wydajność aplikacji. W związku z tym takie podejście zwykle nie jest zalecane i nie jest to metoda używana w tym samouczku.

    Jeśli chcesz zaimplementować te podejście do współbieżności, musisz oznaczyć wszystkie właściwości klucza niepodstawowego w jednostce, dla której chcesz śledzić współbieżność, dodając do nich atrybut ConcurrencyCheck . Ta zmiana umożliwia Entity Framework uwzględnienie wszystkich kolumn w klauzuli SQL WHERE instrukcji UPDATE.

W pozostałej części tego samouczka dodasz Właściwość śledzenia rowversion do jednostki Department, utworzysz kontroler i widoki i testujesz, aby upewnić się, że wszystko działa prawidłowo.

Dodawanie optymistycznej współbieżności

W Models\Department.csDodaj właściwość śledzenia o nazwie 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; }
}

Atrybut timestamp określa, że ta kolumna zostanie uwzględniona w klauzuli Where Update i Delete polecenia wysyłane do bazy danych. Ten atrybut jest nazywany sygnaturą czasową , ponieważ poprzednie wersje SQL Server używały typu danych sygnatur czasowych SQL przed zastąpieniem go przez rowversion . Typem .NET dla rowversion jest tablica bajtów.

Jeśli wolisz używać interfejsu API Fluent, możesz użyć metody IsConcurrencyToken , aby określić właściwość śledzenia, jak pokazano w następującym przykładzie:

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

Przez dodanie właściwości, która zmieniła model bazy danych, należy wykonać kolejną migrację. W konsoli Menedżera pakietów (PMC) wprowadź następujące polecenia:

Add-Migration RowVersion
Update-Database

Modyfikowanie kontrolera działu

W Controllers\DepartmentController.csdodaj instrukcję using:

using System.Data.Entity.Infrastructure;

W pliku DepartmentController.cs Zmień wszystkie cztery wystąpienia "LastName" na "FullName", tak aby listy rozwijane administratorów działu zawierały pełną nazwę instruktora, a nie tylko nazwisko.

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

Zastąp istniejący kod metody HttpPost Edit następującym kodem:

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

Jeśli metoda FindAsync zwraca wartość null, dział został usunięty przez innego użytkownika. Pokazany kod używa opublikowanych wartości formularzy do utworzenia jednostki działu, aby można było ponownie wyświetlić stronę edytowania z komunikatem o błędzie. Alternatywnie nie trzeba ponownie tworzyć jednostki działu, jeśli zostanie wyświetlony komunikat o błędzie bez ponownego wyświetlania pól działu.

Widok przechowuje oryginalną wartość RowVersion w ukrytym polu, a metoda otrzymuje ją w parametrze rowVersion. Przed wywołaniem SaveChangesnależy umieścić tę oryginalną wartość właściwości RowVersion w kolekcji OriginalValues dla jednostki. Następnie, gdy Entity Framework tworzy polecenie SQL UPDATE, to polecenie będzie zawierać klauzulę WHERE, która szuka wiersza, który ma oryginalną wartość RowVersion.

Jeśli nie ma żadnych wierszy, których dotyczy polecenie UPDATE (żadne wiersze nie mają oryginalnej wartości RowVersion), Entity Framework zgłasza wyjątek DbUpdateConcurrencyException, a kod w bloku catch pobiera jednostkę Department, której to dotyczy, z obiektu Exception.

var entry = ex.Entries.Single();

Ten obiekt ma nowe wartości wprowadzone przez użytkownika w jego właściwości Entity i można uzyskać wartości odczytane z bazy danych, wywołując metodę GetDatabaseValues.

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

Metoda GetDatabaseValues zwraca wartość null, jeśli ktoś usunął wiersz z bazy danych. w przeciwnym razie musisz rzutować zwracany obiekt na klasę Department, aby uzyskać dostęp do właściwości Department. (Ponieważ sprawdzono już do usunięcia, databaseEntry będzie miał wartość null tylko wtedy, gdy dział został usunięty po wykonaniu FindAsync i przed wykonaniem SaveChanges.)

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

Następnie kod dodaje niestandardowy komunikat o błędzie dla każdej kolumny, która ma wartości bazy danych inne niż wprowadzone przez użytkownika na stronie edytowania:

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

Dłuższy komunikat o błędzie wyjaśnia, co się stało i co należy zrobić:

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

Na koniec kod ustawia wartość RowVersion obiektu Department na nową wartość pobraną z bazy danych. Ta nowa RowVersion wartość będzie przechowywana w ukrytym polu po ponownym wyświetleniu strony edycji, a przy następnym kliknięciu przycisku Zapiszzostaną przechwycone tylko błędy współbieżności, które zachodzą od momentu wyświetlenia ekranu edycji.

W Views\Department\Edit.cshtmlDodaj pole ukryte, aby zapisać wartość właściwości RowVersion, bezpośrednio po ukrytym polu właściwości DepartmentID:

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

Obsługa współbieżności testów

Uruchom lokację i kliknij pozycję działy.

Kliknij prawym przyciskiem myszy pozycję Edytuj hiperlink dla działu angielskiego i wybierz polecenie Otwórz na nowej karcie, a następnie kliknij hiperłącze Edytuj dla działu angielskiego. Dwie karty zawierają te same informacje.

Zmień pole na pierwszej karcie przeglądarki, a następnie kliknij przycisk Zapisz.

W przeglądarce zostanie wyświetlona strona indeks o zmienionej wartości.

Zmień pole na drugiej karcie przeglądarki, a następnie kliknij przycisk Zapisz. Zostanie wyświetlony komunikat o błędzie:

Department_Edit_page_2_after_clicking_Save

Kliknij przycisk Zapisz ponownie. Wartość wprowadzona na drugiej karcie przeglądarki jest zapisywana wraz z oryginalną wartością danych, które zostały zmienione w pierwszej przeglądarce. Zapisane wartości są wyświetlane, gdy zostanie wyświetlona strona indeks.

Aktualizuj stronę Delete

Na stronie Usuwanie Entity Framework wykrywa konflikty współbieżności spowodowane przez inną osobę edytującą dział w podobny sposób. Gdy metoda HttpGet Delete wyświetla widok potwierdzenia, widok zawiera oryginalną wartość RowVersion w ukrytym polu. Ta wartość jest następnie dostępna dla metody Delete HttpPost, która jest wywoływana, gdy użytkownik potwierdzi usunięcie. Gdy Entity Framework tworzy polecenie SQL DELETE, zawiera klauzulę WHERE z oryginalną wartością RowVersion. Jeśli w poleceniu wyniki są równe zero (oznacza to, że wiersz został zmieniony po wyświetleniu strony potwierdzenia usunięcia), zgłaszany jest wyjątek współbieżności, a metoda HttpGet Delete jest wywoływana z flagą błędu ustawioną na true w celu ponownego wyświetlenia strony potwierdzenia z komunikatem o błędzie. Istnieje również możliwość, że nie wpłynęły na wiersze, ponieważ wiersz został usunięty przez innego użytkownika, więc w takim przypadku zostanie wyświetlony inny komunikat o błędzie.

W DepartmentController.csZastąp metodę Delete HttpGet następującym kodem:

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

Metoda przyjmuje opcjonalny parametr, który wskazuje, czy strona jest ponownie wyświetlana po wystąpieniu błędu współbieżności. Jeśli ta flaga jest true, komunikat o błędzie jest wysyłany do widoku przy użyciu właściwości ViewBag.

Zastąp kod w metodzie Delete HttpPost (o nazwie DeleteConfirmed) następującym kodem:

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

W kodzie szkieletowym, który właśnie został zastąpiony, ta metoda akceptuje tylko identyfikator rekordu:

public async Task<ActionResult> DeleteConfirmed(int id)

Ten parametr został zmieniony do wystąpienia jednostki Department utworzonego przez spinacz modelu. Zapewnia to dostęp do wartości właściwości RowVersion oprócz klucza rekordu.

public async Task<ActionResult> Delete(Department department)

Zmieniono także nazwę metody akcji z DeleteConfirmed na Delete. Kod szkieletu o nazwie HttpPost Delete Metoda DeleteConfirmed, aby dać metodę HttpPost unikatową sygnaturę. (Środowisko CLR wymaga przeciążonych metod, aby mieć inne parametry metody). Teraz, gdy podpisy są unikatowe, można naklejić do Konwencji MVC i używać tej samej nazwy dla HttpPost i HttpGet metody Delete.

W przypadku przechwyconego błędu współbieżności kod ponownie wyświetla stronę potwierdzenia usuwania i zawiera flagę wskazującą, że powinien zostać wyświetlony komunikat o błędzie współbieżności.

W Views\Department\Delete.cshtml, Zastąp kod szkieletowy następującym kodem, który dodaje pole komunikatu o błędzie i ukryte pola właściwości DepartmentID i rowversion. Zmiany są wyróżnione.

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

Ten kod dodaje komunikat o błędzie między nagłówkiem h2 i h3:

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

Zastępuje LastName z FullName w Administrator polu:

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

Na koniec dodaje ukryte pola dla właściwości DepartmentID i RowVersion po instrukcji Html.BeginForm:

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

Uruchom stronę indeksu działów. Kliknij prawym przyciskiem myszy pozycję Usuń hiperłącze dla działu angielskiego i wybierz pozycję Otwórz na nowej karcie, a następnie na pierwszej karcie kliknij hiperłącze Edytuj dla działu angielskiego.

W pierwszym oknie Zmień jedną z wartości, a następnie kliknij przycisk Zapisz.

Strona indeks potwierdza zmianę.

Na drugiej karcie kliknij pozycję Usuń.

Zobaczysz komunikat o błędzie współbieżności, a wartości działu są odświeżane z aktualną wartością w bazie danych.

Department_Delete_confirmation_page_with_concurrency_error

Jeśli klikniesz przycisk Usuń ponownie, nastąpi przekierowanie do strony indeks, która pokazuje, że dział został usunięty.

Uzyskiwanie kodu

Pobierz ukończony projekt

Dodatkowe zasoby

Linki do innych zasobów Entity Framework można znaleźć w zasobach zalecanych przez dostęp do danych ASP.NET.

Aby uzyskać informacje o innych sposobach obsługi różnych scenariuszy współbieżności, zobacz optymistyczne wzorce współbieżności i Praca z wartościami właściwości w witrynie MSDN. W następnym samouczku pokazano, jak zaimplementować dziedziczenie na poziomie tabeli dla jednostek Instructor i Student.

Następne kroki

W tym samouczku zostaną wykonane następujące czynności:

  • Informacje o konfliktach współbieżności
  • Dodano optymistyczną współbieżność
  • Zmodyfikowany kontroler działu
  • Przetestowana obsługa współbieżności
  • Zaktualizowano stronę usuwania

Przejdź do następnego artykułu, aby dowiedzieć się, jak zaimplementować dziedziczenie w modelu danych.