Udostępnij za pośrednictwem


Obsługa współbieżności z programem Entity Framework w aplikacji MVC ASP.NET (7 z 10)

Autor: Tom Dykstra

Przykładowa aplikacja internetowa Contoso University pokazuje, jak tworzyć aplikacje ASP.NET MVC 4 przy użyciu programu Entity Framework 5 Code First i Visual Studio 2012. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek z serii.

Uwaga

Jeśli napotkasz problem, którego nie możesz rozwiązać, pobierz ukończony rozdział i spróbuj odtworzyć problem. Zazwyczaj rozwiązanie problemu można znaleźć, porównując kod z ukończonym kodem. Aby uzyskać informacje o niektórych typowych błędach i sposobach ich rozwiązywania, zobacz Błędy i obejścia.

W dwóch poprzednich samouczkach pracowaliśmy z powiązanymi danymi. W tym samouczku pokazano, jak obsługiwać współbieżność. Utworzysz strony internetowe, które współpracują z jednostką Department , a strony edytujące i usuwające Department jednostki będą obsługiwać błędy współbieżności. Na poniższych ilustracjach przedstawiono strony Indeks i Usuń, w tym niektóre komunikaty wyświetlane w przypadku wystąpienia konfliktu współbieżności.

Zrzut ekranu przedstawia stronę Działy uniwersytetu Contoso przed edycją.

Zrzut ekranu przedstawia stronę Uniwersytetu z komunikatem z informacją, że operacja została anulowana, ponieważ wartość została zmieniona przez innego użytkownika.

Konflikty współbieżności

Konflikt współbieżności występuje, gdy jeden użytkownik wyświetla dane jednostki, aby je edytować, a następnie inny użytkownik aktualizuje dane tej samej jednostki przed zapisaniem zmiany pierwszego użytkownika w bazie danych. Jeśli nie włączysz wykrywania takich konfliktów, kto ostatnio aktualizuje bazę danych, zastępuje zmiany innego użytkownika. W wielu aplikacjach to ryzyko jest dopuszczalne: jeśli istnieje niewielu użytkowników lub kilka aktualizacji, lub jeśli nie jest naprawdę krytyczne, jeśli niektóre zmiany zostaną zastąpione, koszt programowania współbieżności może przeważyć nad korzyścią. W takim przypadku nie trzeba konfigurować aplikacji do obsługi konfliktów współbieżności.

Pesymistyczna współbieżność (blokowanie)

Jeśli aplikacja musi zapobiec przypadkowej utracie danych w scenariuszach współbieżności, jednym ze sposobów jest użycie blokad bazy danych. Jest to nazywane pesymistyczną współbieżnością. Na przykład przed odczytaniem wiersza z bazy danych należy zażądać blokady tylko do odczytu lub dostępu do aktualizacji. Jeśli zablokujesz wiersz dostępu do aktualizacji, żaden inny użytkownik nie może zablokować wiersza w celu uzyskania dostępu tylko do odczytu lub aktualizacji, ponieważ otrzymają kopię danych w procesie zmiany. Jeśli zablokujesz wiersz dostępu tylko do odczytu, inne osoby mogą również zablokować go na potrzeby dostępu tylko do odczytu, ale nie do aktualizacji.

Zarządzanie blokadami ma wady. Może to być skomplikowane do programowania. Wymaga to znaczących zasobów zarządzania bazami danych i może powodować problemy z wydajnością w miarę wzrostu liczby użytkowników aplikacji (czyli nie jest to dobrze skalowane). Z tych powodów nie wszystkie systemy zarządzania bazami danych obsługują pesymistyczną współbieżność. Program Entity Framework nie zapewnia wbudowanej obsługi, a ten samouczek nie pokazuje, jak ją zaimplementować.

Optymistyczna współbieżność

Alternatywą dla pesymistycznej współbieżności jest optymistyczna współbieżność. Optymistyczna współbieżność oznacza umożliwienie wystąpienia konfliktów współbieżności, a następnie odpowiednie reagowanie, jeśli tak. Na przykład Jan uruchamia stronę Edycja Departamentów, zmienia kwotę budżetu dla działu angielskiego z 350 000,000 USD do 0,00 USD.

Changing_English_dept_budget_to_100000

Przed kliknięciem przycisku Zapisz Jane uruchomi tę samą stronę i zmieni pole Data rozpoczęcia z 2007-09-2007 na 8.08.2013.

Changing_English_dept_start_date_to_1999

Jan klika pozycję Zapisz najpierw i widzi jego zmianę po powrocie przeglądarki na stronę Indeks, a następnie jane klika przycisk Zapisz. To, co się stanie dalej, zależy od sposobu obsługi konfliktów współbieżności. Niektóre opcje obejmują następujące opcje:

  • Możesz śledzić, która właściwość użytkownika zmodyfikowała i zaktualizować tylko odpowiednie kolumny w bazie danych. W przykładowym scenariuszu żadne dane nie zostaną utracone, ponieważ dwaj użytkownicy zaktualizowali różne właściwości. Następnym razem, gdy ktoś przegląda angielski dział, zobaczy zmiany Johna i Jane'a — datę rozpoczęcia 8/8/2013 i budżet zero dolarów.

    Ta metoda aktualizowania może zmniejszyć liczbę konfliktów, które mogą spowodować utratę danych, ale nie może uniknąć utraty danych, jeśli konkurencyjne zmiany zostaną wprowadzone do tej samej właściwości jednostki. To, czy program Entity Framework działa w ten sposób, zależy od sposobu implementacji kodu aktualizacji. Często nie jest to praktyczne w aplikacji internetowej, ponieważ może wymagać utrzymania dużych ilości stanu w celu śledzenia wszystkich oryginalnych wartości właściwości dla jednostki, a także nowych wartości. Utrzymywanie dużych ilości stanu może mieć wpływ na wydajność aplikacji, ponieważ wymaga zasobów serwera lub musi zostać uwzględnione na samej stronie internetowej (na przykład w ukrytych polach).

  • Możesz pozwolić, aby zmiana Jane zastąpiła zmianę Johna. Następnym razem, gdy ktoś przegląda angielski dział, zobaczy 8/8/2013 i przywróconą wartość $350,000.000. Jest to nazywane scenariuszem wins klienta lub Last in Wins . (Wartości klienta mają pierwszeństwo przed tym, co znajduje się w magazynie danych). Jak wspomniano we wprowadzeniu do tej sekcji, jeśli nie zrobisz żadnego kodowania na potrzeby obsługi współbieżności, nastąpi to automatycznie.

  • Możesz uniemożliwić aktualizację jane w bazie danych. Zazwyczaj jest wyświetlany komunikat o błędzie, pokazywanie jej bieżącego stanu danych i umożliwianie jej ponownego zastosowania zmian, jeśli nadal chce je wprowadzić. Jest to nazywane scenariuszem Store Wins . (Wartości magazynu danych mają pierwszeństwo przed wartościami przesłanimi przez klienta). W tym samouczku zaimplementujesz scenariusz Store Wins. Ta metoda gwarantuje, że żadne zmiany nie zostaną zastąpione bez powiadamiania użytkownika o tym, co się dzieje.

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

Konflikty można rozwiązać, obsługując wyjątki OptimisticConcurrencyException zgłaszane przez program Entity Framework. Aby wiedzieć, kiedy zgłaszać te wyjątki, program Entity Framework musi mieć możliwość wykrywania konfliktów. W związku z tym należy odpowiednio skonfigurować bazę danych i model danych. Niektóre opcje włączania wykrywania konfliktów obejmują następujące elementy:

  • W tabeli bazy danych dołącz kolumnę śledzenia, która może służyć do określenia, kiedy wiersz został zmieniony. Następnie można skonfigurować program Entity Framework tak, aby zawierał kolumnę Where w klauzuli SQL Update lub Delete poleceniach.

    Typ danych kolumny śledzenia to zazwyczaj rowversion. Wartość rowversion jest sekwencyjną liczbą, która jest zwiększana za każdym razem, gdy wiersz jest aktualizowany. W poleceniu 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 rowversion kolumnie różni się od oryginalnej wartości, więc Update instrukcja or Delete nie może znaleźć wiersza do zaktualizowania z Where powodu klauzuli . Gdy program Entity Framework wykryje, że żadne wiersze nie zostały zaktualizowane przez Update polecenie lub Delete (czyli gdy liczba wierszy, których dotyczy problem, wynosi zero), interpretuje to jako konflikt współbieżności.

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

    Podobnie jak w przypadku pierwszej opcji, jeśli coś w wierszu uległo zmianie od czasu pierwszego odczytania wiersza, Where klauzula nie zwróci wiersza do zaktualizowania, który program Entity Framework interpretuje jako konflikt współbieżności. W przypadku tabel baz danych, które mają wiele kolumn, takie podejście może spowodować bardzo duże Where klauzule i może wymagać obsługi dużych ilości stanu. Jak wspomniano wcześniej, utrzymywanie dużych ilości stanu może mieć wpływ na wydajność aplikacji, ponieważ wymaga zasobów serwera lub musi zostać uwzględnione na samej stronie internetowej. 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ć to podejście do współbieżności, musisz oznaczyć wszystkie właściwości innej niż klucz podstawowy w jednostce, dla której chcesz śledzić współbieżność, dodając do nich atrybut ConcurrencyCheck . Ta zmiana umożliwia programowi Entity Framework uwzględnienie wszystkich kolumn w klauzuli SQL WHERE instrukcji UPDATE .

W pozostałej części tego samouczka dodasz do jednostki właściwość Department śledzenia rowversion, utworzysz kontroler i widoki i przetestujesz, aby sprawdzić, czy wszystko działa poprawnie.

Dodawanie właściwości optymistycznej współbieżności do jednostki Działu

W obszarze Models\Department.cs dodaj 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)]
    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 Sygnatura czasowa określa, że ta kolumna zostanie uwzględniona w Where klauzuli Update i Delete polecenia wysyłane do bazy danych. Atrybut jest nazywany sygnaturą czasową, ponieważ poprzednie wersje programu SQL Server używały typu danych sygnatury czasowej SQL przed zastąpieniem go przez element rowversion sql. Typ platformy .Net dla rowversion jest tablicą bajtów. Jeśli wolisz używać płynnego interfejsu API, możesz użyć metody IsConcurrencyToken , aby określić właściwość śledzenia, jak pokazano w poniższym przykładzie:

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

Zobacz zastąpienie problemu z usługą GitHub IsConcurrencyToken przez IsRowVersion.

Dodając właściwość, którą zmieniono model bazy danych, należy więc przeprowadzić inną migrację. W konsoli Menedżer pakietów (PMC) wprowadź następujące polecenia:

Add-Migration RowVersion
Update-Database

Tworzenie kontrolera działu

Department Utwórz kontroler i widoki tak samo jak inne kontrolery, używając następujących ustawień:

Add_Controller_dialog_box_for_Department_controller

W obszarze Controllers\DepartmentController.cs dodaj instrukcję using :

using System.Data.Entity.Infrastructure;

Zmień wartość "LastName" na "FullName" wszędzie w tym pliku (cztery wystąpienia), aby listy rozwijane administratora działu zawierały pełną nazwę instruktora, a nie tylko nazwisko.

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

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

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

Widok będzie przechowywać oryginalną RowVersion wartość w ukrytym polu. Gdy binder modelu tworzy department wystąpienie, ten obiekt będzie miał oryginalną RowVersion wartość właściwości i nowe wartości dla innych właściwości, zgodnie z wprowadzonym przez użytkownika na stronie Edycja. Następnie, gdy program Entity Framework utworzy polecenie SQL UPDATE , to polecenie będzie zawierać klauzulę WHERE , która wyszukuje wiersz zawierający oryginalną RowVersion wartość.

Jeśli polecenie nie ma wpływu na UPDATE żadne wiersze (żadne wiersze nie mają oryginalnej RowVersion wartości), program Entity Framework zgłasza DbUpdateConcurrencyException wyjątek, a kod w catch bloku pobiera jednostkę, której dotyczy problem Department , z obiektu wyjątku. Ta jednostka ma zarówno wartości odczytane z bazy danych, jak i nowe wartości wprowadzone przez użytkownika:

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

Następnie kod dodaje niestandardowy komunikat o błędzie dla każdej kolumny zawierającej wartości bazy danych inne niż to, co użytkownik wprowadził na stronie Edycja:

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 z tym 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 RowVersion wartość Department obiektu na nową wartość pobraną z bazy danych. Ta nowa RowVersion wartość będzie przechowywana w ukrytym polu, gdy strona Edytuj zostanie ponownie odtworzona, a następnym razem użytkownik kliknie pozycję Zapisz, zostaną przechwycone tylko błędy współbieżności występujące od czasu ponownego redysponowania strony Edycja.

W pliku Views\Department\Edit.cshtml dodaj ukryte pole, aby zapisać RowVersion wartość właściwości, 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()
    @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>

W pliku Views\Department\Index.cshtml zastąp istniejący kod następującym kodem, aby przenieść linki wierszy do lewej strony i zmienić tytuł strony i nagłówki kolumn, aby wyświetlić FullName zamiast LastName w kolumnie Administrator :

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

Testowanie optymistycznej obsługi współbieżności

Uruchom witrynę i kliknij pozycję Działy:

Zrzut ekranu przedstawiający stronę Działy uniwersytetu Contoso.

Kliknij prawym przyciskiem myszy hiperłącze Edytuj dla kim Abercrombie i wybierz polecenie Otwórz na nowej karcie, a następnie kliknij hiperlink Edytuj kim Abercrombie. Dwa okna zawierają te same informacje.

Department_Edit_page_before_changes

Zmień pole w pierwszym oknie przeglądarki i kliknij przycisk Zapisz.

Department_Edit_page_1_after_change

W przeglądarce zostanie wyświetlona strona Indeks ze zmienioną wartością.

Departments_Index_page_after_first_budget_edit

Zmień dowolne pole w drugim oknie przeglądarki i kliknij przycisk Zapisz.

Department_Edit_page_2_after_change

Kliknij przycisk Zapisz w drugim oknie przeglądarki. Zostanie wyświetlony komunikat o błędzie:

Zrzut ekranu przedstawiający stronę Uniwersytet z komunikatem o błędzie gotowym dla użytkownika do ponownego wybrania pozycji Zapisz.

Kliknij ponownie przycisk Zapisz . Wartość wprowadzona w drugiej przeglądarce jest zapisywana wraz z oryginalną wartością danych zmienianych w pierwszej przeglądarce. Zapisane wartości są widoczne po wyświetleniu strony Indeks.

Department_Index_page_with_change_from_second_browser

Aktualizowanie strony usuwania

Na stronie Usuwanie platforma Entity Framework wykrywa konflikty współbieżności spowodowane przez inną osobę edytując dział w podobny sposób. HttpGet Delete Gdy metoda wyświetli widok potwierdzenia, widok zawiera oryginalną RowVersion wartość w polu ukrytym. Ta wartość jest następnie dostępna dla HttpPost Delete metody wywoływanej, gdy użytkownik potwierdzi usunięcie. Gdy program Entity Framework utworzy polecenie SQL DELETE , zawiera klauzulę WHERE z oryginalną RowVersion wartością. Jeśli polecenie powoduje, że nie ma to wpływu na wiersze (co oznacza, że wiersz został zmieniony po wyświetleniu strony potwierdzenia Usuń), zostanie zgłoszony wyjątek współbieżności, a HttpGet Delete metoda jest wywoływana z flagą błędu ustawioną na true w celu ponownego redysponowania strony potwierdzenia z komunikatem o błędzie. Istnieje również możliwość, że nie ma to wpływu na wiersze, ponieważ wiersz został usunięty przez innego użytkownika, więc w takim przypadku jest wyświetlany inny komunikat o błędzie.

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

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

Metoda akceptuje opcjonalny parametr wskazujący, czy strona jest odtwarzana ponownie po błędzie współbieżności. Jeśli ta flaga to true, do widoku jest wysyłany komunikat o błędzie przy użyciu ViewBag właściwości.

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

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

W kodzie szkieletowym, który właśnie zamieniono, ta metoda zaakceptowała tylko identyfikator rekordu:

public ActionResult DeleteConfirmed(int id)

Ten parametr został zmieniony na Department wystąpienie jednostki utworzone przez powiązanie modelu. Zapewnia to dostęp do RowVersion wartości właściwości oprócz klucza rekordu.

public ActionResult Delete(Department department)

Zmieniono również nazwę metody akcji z DeleteConfirmed na Delete. Kod szkieletowy o nazwie HttpPost Delete metody DeleteConfirmed w celu nadania metodzie HttpPost unikatowego podpisu. (ClR wymaga przeciążonych metod, aby mieć różne parametry metody). Teraz, gdy podpisy są unikatowe, możesz trzymać się konwencji MVC i używać tej samej nazwy dla HttpPost metod i HttpGet usuwania.

Jeśli zostanie przechwycony błąd współbieżności, kod ponownie wyświetla stronę potwierdzenia Usuń i udostępnia flagę wskazującą, że powinien wyświetlić komunikat o błędzie współbieżności.

W pliku Views\Department\Delete.cshtml zastąp kod szkieletowy następującym kodem, który wprowadza pewne zmiany formatowania i dodaje pole komunikatu o błędzie. 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>
<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>
}

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

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

Zastępuje LastName ciąg ciągiem FullName Administrator w polu :

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

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

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

Uruchom stronę Indeks działów. Kliknij prawym przyciskiem myszy hiperlink Usuń dla działu angielskiego i wybierz polecenie Otwórz w nowym oknie, a następnie w pierwszym oknie kliknij hiperlink Edytuj dla działu angielskiego.

W pierwszym oknie zmień jedną z wartości, a następnie kliknij pozycję Zapisz :

Department_Edit_page_after_change_before_delete

Strona Indeks potwierdza zmianę.

Departments_Index_page_after_budget_edit_before_delete

W drugim oknie kliknij pozycję Usuń.

Department_Delete_confirmation_page_before_concurrency_error

Zostanie wyświetlony komunikat o błędzie współbieżności, a wartości Dział są odświeżane z bieżącymi elementami w bazie danych.

Department_Delete_confirmation_page_with_concurrency_error

Po ponownym kliknięciu przycisku Usuń nastąpi przekierowanie do strony Indeks, która pokazuje, że dział został usunięty.

Podsumowanie

Spowoduje to ukończenie wprowadzenia do obsługi konfliktów współbieżności. Aby uzyskać informacje na temat innych sposobów 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 blogu zespołu platformy Entity Framework. W następnym samouczku pokazano, jak zaimplementować dziedziczenie tabeli na hierarchię dla jednostek Instructor i Student .

Linki do innych zasobów programu Entity Framework można znaleźć na mapie zawartości dostępu do danych ASP.NET.