Aktualizowanie powiązanych danych za pomocą platformy Entity Framework w aplikacji MVC ASP.NET (6 z 10)

Autor : Tom Dykstra

Przykładowa aplikacja internetowa Contoso University pokazuje, jak utworzyć 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 poprzednim samouczku były wyświetlane powiązane dane; w tym samouczku zaktualizujesz powiązane dane. W przypadku większości relacji można to zrobić, aktualizując odpowiednie pola klucza obcego. W przypadku relacji wiele-do-wielu platforma Entity Framework nie uwidacznia tabeli sprzężenia bezpośrednio, dlatego należy jawnie dodawać i usuwać jednostki do i z odpowiednich właściwości nawigacji.

Na poniższych ilustracjach przedstawiono strony, z którymi będziesz pracować.

Zrzut ekranu przedstawiający stronę Tworzenie kursu.

Zrzut ekranu przedstawiający stronę Edytowanie instruktora.

Dostosowywanie stron tworzenia i edytowania dla kursów

Po utworzeniu nowej jednostki kursu musi ona mieć relację z istniejącym działem. Aby to ułatwić, kod szkieletowy zawiera metody kontrolera oraz widoki tworzenia i edytowania, które zawierają listę rozwijaną do wybierania działu. Lista rozwijana ustawia właściwość klucza obcego Course.DepartmentID , a to wszystko, co wymaga platformy Entity Framework w celu załadowania Department właściwości nawigacji z odpowiednią Department jednostką. Użyjesz kodu szkieletowego, ale zmienisz go nieco, aby dodać obsługę błędów i posortować listę rozwijaną.

W pliku CourseController.cs usuń cztery Edit metody i i Create zastąp je następującym kodem:

public ActionResult Create()
{
   PopulateDepartmentsDropDownList();
   return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(
   [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
   Course course)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Courses.Add(course);
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException 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.");
   }
   PopulateDepartmentsDropDownList(course.DepartmentID);
   return View(course);
}

public ActionResult Edit(int id)
{
   Course course = db.Courses.Find(id);
   PopulateDepartmentsDropDownList(course.DepartmentID);
   return View(course);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
    [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
    Course course)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(course).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException 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.");
   }
   PopulateDepartmentsDropDownList(course.DepartmentID);
   return View(course);
}

private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
   var departmentsQuery = from d in db.Departments
                          orderby d.Name
                          select d;
   ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
} 

Metoda PopulateDepartmentsDropDownList pobiera listę wszystkich działów posortowanych według nazwy, tworzy SelectList kolekcję listy rozwijanej ViewBag i przekazuje kolekcję do widoku we właściwości. Metoda akceptuje opcjonalny selectedDepartment parametr, który umożliwia kod wywołujący określenie elementu, który zostanie wybrany podczas renderowania listy rozwijanej. Widok przekaże nazwę DepartmentIDpomocnikowiDropDownList, a pomocnik będzie w stanie wyszukać w ViewBag obiekcie SelectList o nazwie DepartmentID.

Metoda HttpGetCreate wywołuje metodę PopulateDepartmentsDropDownList bez ustawiania wybranego elementu, ponieważ dla nowego kursu dział nie został jeszcze ustanowiony:

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

Metoda HttpGetEdit ustawia wybrany element na podstawie identyfikatora działu, który jest już przypisany do edytowanego kursu:

public ActionResult Edit(int id)
{
    Course course = db.Courses.Find(id);
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

HttpPost Metody obu tych CreateEdit metod obejmują również kod, który ustawia wybrany element po ponownym uruchomieniu strony po błędzie:

catch (DataException /* dex */)
{
    //Log the error (uncomment dex variable name after DataException 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.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);

Ten kod gwarantuje, że po ponownym uruchomieniu strony w celu wyświetlenia komunikatu o błędzie wybrany dział pozostaje wybrany.

W obszarze Views\Course\Create.cshtml dodaj wyróżniony kod, aby utworzyć nowe pole numeru kursu przed polem Tytuł . Jak wyjaśniono we wcześniejszym samouczku, pola klucza podstawowego nie są domyślnie szkieletowe, ale ten klucz podstawowy ma znaczenie, więc chcesz, aby użytkownik mógł wprowadzić wartość klucza.

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

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

    <fieldset>
        <legend>Course</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.CourseID)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.CourseID)
            @Html.ValidationMessageFor(model => model.CourseID)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Credits)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Credits)
            @Html.ValidationMessageFor(model => model.Credits)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.DepartmentID, "Department")
        </div>
        <div class="editor-field">
            @Html.DropDownList("DepartmentID", String.Empty)
            @Html.ValidationMessageFor(model => model.DepartmentID)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

W obszarze Views\Course\Edit.cshtml, Views\Course\Delete.cshtml i Views\Course\Details.cshtml dodaj pole numeru kursu przed polem Tytuł . Ponieważ jest to klucz podstawowy, jest wyświetlany, ale nie można go zmienić.

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

Uruchom stronę Tworzenie (wyświetl stronę Indeks kursu i kliknij przycisk Utwórz nowy) i wprowadź dane dla nowego kursu:

Course_create_page

Kliknij pozycję Utwórz. Zostanie wyświetlona strona Indeks kursu z nowym kursem dodanym do listy. Nazwa działu na liście strony Indeks pochodzi z właściwości nawigacji, co pokazuje, że relacja została prawidłowo ustanowiona.

Course_Index_page_showing_new_course

Uruchom stronę Edytuj (wyświetl stronę Indeks kursu i kliknij przycisk Edytuj na kursie).

Course_edit_page

Zmień dane na stronie i kliknij przycisk Zapisz. Strona Indeks kursu jest wyświetlana ze zaktualizowanymi danymi kursu.

Dodawanie strony edycji dla instruktorów

Podczas edytowania rekordu instruktora chcesz mieć możliwość zaktualizowania przypisania biura instruktora. Jednostka Instructor ma relację jeden do zera lub jednego z jednostką OfficeAssignment , co oznacza, że musisz obsługiwać następujące sytuacje:

  • Jeśli użytkownik wyczyści przypisanie pakietu Office i pierwotnie miał wartość, musisz usunąć i usunąć OfficeAssignment jednostkę.
  • Jeśli użytkownik wprowadzi wartość przypisania pakietu Office i pierwotnie był pusty, musisz utworzyć nową OfficeAssignment jednostkę.
  • Jeśli użytkownik zmieni wartość przypisania pakietu Office, musisz zmienić wartość w istniejącej OfficeAssignment jednostce.

Otwórz plik InstructorController.cs i przyjrzyj się metodzie HttpGetEdit :

public ActionResult Edit(int id = 0)
{
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.InstructorID);
    return View(instructor);
}

Kod szkieletowy w tym miejscu nie jest odpowiedni. Konfiguruje ona dane dla listy rozwijanej, ale to, czego potrzebujesz, to pole tekstowe. Zastąp tę metodę następującym kodem:

public ActionResult Edit(int id)
{
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Where(i => i.InstructorID == id)
        .Single();
    return View(instructor);
}

Ten kod przerywa instrukcję ViewBag i dodaje chętne ładowanie dla skojarzonej OfficeAssignment jednostki. Nie można wykonać chętnego ładowania za Find pomocą metody , więc Where metody i Single są używane do wybierania instruktora.

Zastąp metodę HttpPostEdit następującym kodem. który obsługuje aktualizacje przypisań pakietu Office:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection)
{
   var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.InstructorID == id)
       .Single();

   if (TryUpdateModel(instructorToUpdate, "",
      new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
   {
      try
      {
         if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
         {
            instructorToUpdate.OfficeAssignment = null;
         }

         db.Entry(instructorToUpdate).State = EntityState.Modified;
         db.SaveChanges();

         return RedirectToAction("Index");
      }
      catch (DataException /* dex */)
      {
         //Log the error (uncomment dex variable name after DataException 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.OfficeAssignments, "InstructorID", "Location", id);
   return View(instructorToUpdate);
}

Kod wykonuje następujące czynności:

  • Pobiera bieżącą Instructor jednostkę z bazy danych przy użyciu chętnego OfficeAssignment ładowania dla właściwości nawigacji. Jest to takie samo, jak w metodzie HttpGetEdit .

  • Aktualizacje pobraną Instructor jednostkę z wartościami z powiązania modelu. Używane przeciążenie TryUpdateModel umożliwia bezpieczną listę właściwości, które chcesz uwzględnić. Zapobiega to nadmiernemu publikowaniu, jak wyjaśniono w drugim samouczku.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Jeśli lokalizacja biura jest pusta, ustawia Instructor.OfficeAssignment właściwość na null, tak aby powiązany wiersz w OfficeAssignment tabeli został usunięty.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Zapisuje zmiany w bazie danych.

W obszarze Views\Instructor\Edit.cshtml po div elementach pola Data zatrudnienia dodaj nowe pole do edycji lokalizacji biura:

<div class="editor-label">
    @Html.LabelFor(model => model.OfficeAssignment.Location)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.OfficeAssignment.Location)
    @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>

Uruchom stronę (wybierz kartę Instruktorzy , a następnie kliknij pozycję Edytuj na instruktorze). Zmień lokalizację pakietu Office i kliknij przycisk Zapisz.

Changing_the_office_location

Dodawanie przypisań kursów do strony edycji instruktora

Instruktorzy mogą uczyć dowolną liczbę kursów. Teraz ulepszysz stronę Edytowanie instruktora, dodając możliwość zmiany przypisań kursu przy użyciu grupy pól wyboru, jak pokazano na poniższym zrzucie ekranu:

Zrzut ekranu przedstawiający stronę Edytowanie instruktora z kursami.

Relacja między jednostkami Course i Instructor to wiele do wielu, co oznacza, że nie masz bezpośredniego dostępu do tabeli sprzężenia. Zamiast tego dodasz i usuniesz jednostki do i z Instructor.Courses właściwości nawigacji.

Interfejs użytkownika, który umożliwia zmianę kursów przypisanych przez instruktora, jest grupą pól wyboru. Zostanie wyświetlone pole wyboru dla każdego kursu w bazie danych, a wybrane są te, do których jest obecnie przypisany instruktor. Użytkownik może zaznaczyć lub wyczyścić pola wyboru, aby zmienić przypisania kursu. Jeśli liczba kursów była znacznie większa, prawdopodobnie chcesz użyć innej metody prezentowania danych w widoku, ale użyjesz tej samej metody manipulowania właściwościami nawigacji, aby utworzyć lub usunąć relacje.

Aby podać dane do widoku listy pól wyboru, użyjesz klasy modelu widoku. Utwórz plik AssignedCourseData.cs w folderze ViewModels i zastąp istniejący kod następującym kodem:

namespace ContosoUniversity.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

W pliku InstructorController.cs zastąp metodę HttpGetEdit poniższym kodem. Zmiany są wyróżnione.

public ActionResult Edit(int id)
{
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.InstructorID == id)
        .Single();
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

private void PopulateAssignedCourseData(Instructor instructor)
{
    var allCourses = db.Courses;
    var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
    var viewModel = new List<AssignedCourseData>();
    foreach (var course in allCourses)
    {
        viewModel.Add(new AssignedCourseData
        {
            CourseID = course.CourseID,
            Title = course.Title,
            Assigned = instructorCourses.Contains(course.CourseID)
        });
    }
    ViewBag.Courses = viewModel;
}

Kod dodaje chętne Courses ładowanie dla właściwości nawigacji i wywołuje nową PopulateAssignedCourseData metodę w celu udostępnienia informacji dla tablicy pól wyboru przy użyciu AssignedCourseData klasy modelu widoku.

Kod w metodzie PopulateAssignedCourseData odczytuje wszystkie Course jednostki w celu załadowania listy kursów przy użyciu klasy modelu widoku. Dla każdego kursu kod sprawdza, czy kurs istnieje we właściwości nawigacji instruktora Courses . Aby utworzyć efektywne wyszukiwanie podczas sprawdzania, czy kurs jest przypisany do instruktora, kursy przypisane do instruktora są umieszczane w kolekcji HashSet . Właściwość jest ustawiona Assigned na true kursy, do których jest przypisany instruktor. Widok użyje tej właściwości, aby określić, które pola wyboru muszą być wyświetlane jako zaznaczone. Na koniec lista jest przekazywana do widoku we ViewBag właściwości.

Następnie dodaj kod wykonywany po kliknięciu przycisku Zapisz przez użytkownika. Zastąp metodę HttpPostEdit następującym kodem, który wywołuje nową metodę, która aktualizuje Courses właściwość Instructor nawigacji jednostki. Zmiany są wyróżnione.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection, string[] selectedCourses)
{
   var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Include(i => i.Courses)
       .Where(i => i.InstructorID == id)
       .Single();
   if (TryUpdateModel(instructorToUpdate, "", 
      new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
   {
      try
      {
         if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
         {
            instructorToUpdate.OfficeAssignment = null;
         }

         UpdateInstructorCourses(selectedCourses, instructorToUpdate);

         db.Entry(instructorToUpdate).State = EntityState.Modified;
         db.SaveChanges();

         return RedirectToAction("Index");
      }
      catch (DataException /* dex */)
      {
         //Log the error (uncomment dex variable name after DataException 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.");
      }
   }
   PopulateAssignedCourseData(instructorToUpdate);
   return View(instructorToUpdate);
}

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
   if (selectedCourses == null)
   {
      instructorToUpdate.Courses = new List<Course>();
      return;
   }

   var selectedCoursesHS = new HashSet<string>(selectedCourses);
   var instructorCourses = new HashSet<int>
       (instructorToUpdate.Courses.Select(c => c.CourseID));
   foreach (var course in db.Courses)
   {
      if (selectedCoursesHS.Contains(course.CourseID.ToString()))
      {
         if (!instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Add(course);
         }
      }
      else
      {
         if (instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Remove(course);
         }
      }
   }
}

Ponieważ widok nie ma kolekcji Course jednostek, powiązanie modelu nie może automatycznie zaktualizować Courses właściwości nawigacji. Zamiast używać powiązania modelu w celu zaktualizowania właściwości nawigacji Courses, należy to zrobić w nowej UpdateInstructorCourses metodzie. W związku z tym należy wykluczyć Courses właściwość z powiązania modelu. Nie wymaga to żadnych zmian w kodzie, który wywołuje metodę TryUpdateModel , ponieważ używasz przeciążenia listy bezpiecznych i Courses nie znajduje się na liście dołączania.

Jeśli nie zaznaczono żadnych pól wyboru, kod inicjuje UpdateInstructorCoursesCourses właściwość nawigacji z pustą kolekcją:

if (selectedCourses == null)
{
    instructorToUpdate.Courses = new List<Course>();
    return;
}

Następnie kod przechodzi przez wszystkie kursy w bazie danych i sprawdza każdy kurs względem aktualnie przypisanych do instruktora w porównaniu z tymi, które zostały wybrane w widoku. Aby ułatwić wydajne wyszukiwanie, dwie ostatnie kolekcje są przechowywane w HashSet obiektach.

Jeśli pole wyboru kursu zostało wybrane, ale kurs nie znajduje się we Instructor.Courses właściwości nawigacji, kurs zostanie dodany do kolekcji we właściwości nawigacji.

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Add(course);
    }
}

Jeśli pole wyboru kursu nie zostało zaznaczone, ale kurs znajduje się we Instructor.Courses właściwości nawigacji, kurs zostanie usunięty z właściwości nawigacji.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Remove(course);
    }
}

W pliku Views\Instructor\Edit.cshtml dodaj pole Courses z tablicą pól wyboru, dodając następujący wyróżniony kod bezpośrednio po div elementach OfficeAssignment pola:

@model ContosoUniversity.Models.Instructor

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

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

    <fieldset>
        <legend>Instructor</legend>

        @Html.HiddenFor(model => model.InstructorID)

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstMidName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstMidName)
            @Html.ValidationMessageFor(model => model.FirstMidName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.HireDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.HireDate)
            @Html.ValidationMessageFor(model => model.HireDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.OfficeAssignment.Location)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.OfficeAssignment.Location)
            @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
        </div>

        <div class="editor-field">
    <table>
        <tr>
            @{
                int cnt = 0;
                List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                foreach (var course in courses) {
                    if (cnt++ % 3 == 0) {
                        @:  </tr> <tr> 
                    }
                    @: <td> 
                        <input type="checkbox" 
                               name="selectedCourses" 
                               value="@course.CourseID" 
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) /> 
                        @course.CourseID @:  @course.Title
                    @:</td>
                }
                @: </tr>
            }
    </table>
</div>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Ten kod tworzy tabelę HTML zawierającą trzy kolumny. W każdej kolumnie jest polem wyboru, po którym następuje podpis składający się z numeru kursu i tytułu. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"), która informuje powiązanie modelu, że mają być traktowane jako grupa. Atrybut value każdego pola wyboru jest ustawiony na wartość CourseID. Po opublikowaniu strony, binder modelu przekazuje tablicę do kontrolera, który składa się z CourseID wartości tylko zaznaczonych pól wyboru.

Gdy pola wyboru są początkowo renderowane, te, które są przeznaczone do kursów przypisanych do instruktora, mają checked atrybuty, które je wybierają (wyświetla je zaznaczone).

Po zmianie przypisań kursu warto sprawdzić zmiany, gdy witryna powróci do Index strony. W związku z tym należy dodać kolumnę do tabeli na tej stronie. W takim przypadku nie musisz używać ViewBag obiektu, ponieważ informacje, które chcesz wyświetlić, są już we Courses właściwości Instructor nawigacji jednostki, którą przekazujesz do strony jako modelu.

W pliku Views\Instructor\Index.cshtml dodaj nagłówek Kursy bezpośrednio po nagłówku pakietu Office , jak pokazano w poniższym przykładzie:

<tr> 
    <th></th> 
    <th>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
</tr>

Następnie dodaj nową komórkę szczegółów bezpośrednio po komórce szczegółów lokalizacji biura:

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
    ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Hire Date</th>
        <th>Office</th>
        <th>Courses</th>
    </tr>
    @foreach (var item in Model.Instructors)
    {
        string selectedRow = "";
        if (item.InstructorID == ViewBag.InstructorID)
        {
            selectedRow = "selectedrow";
        } 
        <tr class="@selectedRow" valign="top">
            <td>
                @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) | 
                @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) | 
                @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) | 
                @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID })
            </td>
            <td>
                @item.LastName
            </td>
            <td>
                @item.FirstMidName
            </td>
            <td>
                @String.Format("{0:d}", item.HireDate)
            </td>
            <td>
                @if (item.OfficeAssignment != null)
                { 
                    @item.OfficeAssignment.Location  
                }
            </td>
            <td>
                @{
                foreach (var course in item.Courses)
                {
                    @course.CourseID @:  @course.Title <br />
                }
                }
            </td>
        </tr> 
    }
</table>

@if (Model.Courses != null)
{ 
    <h3>Courses Taught by Selected Instructor</h3> 
    <table>
        <tr>
            <th></th>
            <th>ID</th>
            <th>Title</th>
            <th>Department</th>
        </tr>

        @foreach (var item in Model.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == ViewBag.CourseID)
            {
                selectedRow = "selectedrow";
            } 
        
            <tr class="@selectedRow">

                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr> 
        }

    </table> 
}
@if (Model.Enrollments != null)
{ 
    <h3>Students Enrolled in Selected Course</h3> 
    <table>
        <tr>
            <th>Name</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        { 
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr> 
        }
    </table> 
}

Uruchom stronę Indeks instruktora , aby zobaczyć kursy przypisane do każdego instruktora:

Instructor_index_page

Kliknij pozycję Edytuj na instruktorze, aby wyświetlić stronę Edytuj.

Instructor_edit_page_with_courses

Zmień niektóre przypisania kursu i kliknij przycisk Zapisz. Wprowadzone zmiany zostaną odzwierciedlone na stronie Indeks.

Uwaga: Podejście podjęte do edytowania danych kursu instruktora działa dobrze, gdy istnieje ograniczona liczba kursów. W przypadku kolekcji, które są znacznie większe, wymagany jest inny interfejs użytkownika i inna metoda aktualizacji.

Aktualizowanie metody Delete

Zmień kod w metodzie HttpPost Delete, aby rekord przypisania pakietu Office (jeśli istnieje) został usunięty po usunięciu instruktora:

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
   Instructor instructor = db.Instructors
     .Include(i => i.OfficeAssignment)
     .Where(i => i.InstructorID == id)
     .Single();

   instructor.OfficeAssignment = null;
   db.Instructors.Remove(instructor);
   db.SaveChanges();
   return RedirectToAction("Index");
}

Jeśli spróbujesz usunąć instruktora przypisanego do działu jako administrator, otrzymasz błąd integralności referencyjnej. Zobacz bieżącą wersję tego samouczka , aby uzyskać dodatkowy kod, który automatycznie usunie instruktora z dowolnego działu, w którym instruktor jest przypisany jako administrator.

Podsumowanie

Ukończono to wprowadzenie do pracy z powiązanymi danymi. Do tej pory w tych samouczkach wykonaliśmy pełną gamę operacji CRUD, ale nie masz do czynienia z problemami współbieżności. W następnym samouczku przedstawiono temat współbieżności, objaśnienie opcji obsługi i dodanie obsługi współbieżności do kodu CRUD, który został już napisany dla jednego typu jednostki.

Linki do innych zasobów platformy Entity Framework można znaleźć na końcu ostatniego samouczka w tej serii.