Samouczek: aktualizowanie powiązanych danych za pomocą platformy EF w aplikacji MVC ASP.NET

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 pola klucza obcego lub właściwości nawigacji. W przypadku relacji wiele-do-wielu struktura Entity Framework nie uwidacznia tabeli sprzężenia bezpośrednio, dlatego dodawaj i usuwasz jednostki do i z odpowiednich właściwości nawigacji.

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

Course_create_page

Instructor_edit_page_with_courses

Edytowanie instruktora za pomocą kursów

W tym samouczku zostały wykonane następujące czynności:

  • Dostosowywanie stron kursów
  • Dodawanie biura do strony instruktorów
  • Dodawanie kursów do strony instruktorów
  • Aktualizuj polecenie DeleteConfirmed
  • Dodawanie lokalizacji i kursów biura do strony Tworzenie

Wymagania wstępne

Dostosowywanie stron 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 Create metody i i Edit 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 (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.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var courseToUpdate = db.Courses.Find(id);
    if (TryUpdateModel(courseToUpdate, "",
       new string[] { "Title", "Credits", "DepartmentID" }))
    {
        try
        {
            db.SaveChanges();

            return RedirectToAction("Index");
        }
        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.");
        }
    }
    PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
    return View(courseToUpdate);
}

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

Dodaj następującą using instrukcję na początku pliku:

using System.Data.Entity.Infrastructure;

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ę DepartmentID do pomocnika DropDownList , 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)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    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 (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.");
}
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.

Widoki kursu są już utworzone szkieletem z listami rozwijanymi dla pola działu, ale nie chcesz, aby identyfikator Działu podpis dla tego pola, dlatego wprowadź następującą wyróżnioną zmianę w pliku Views\Course\Create.cshtml, aby zmienić podpis.

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Course</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CourseID)
                @Html.ValidationMessageFor(model => model.CourseID)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Credits)
                @Html.ValidationMessageFor(model => model.Credits)
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="DepartmentID">Department</label>
            <div class="col-md-10">
                @Html.DropDownList("DepartmentID", String.Empty)
                @Html.ValidationMessageFor(model => model.DepartmentID)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

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

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

Wprowadź tę samą zmianę w pliku Views\Course\Edit.cshtml.

Zwykle szkielet nie tworzy szkieletu klucza podstawowego, ponieważ wartość klucza jest generowana przez bazę danych i nie można jej zmienić i nie jest znaczącą wartością do wyświetlania użytkownikom. W przypadku jednostek kursu rusztator zawiera pole tekstowe dla CourseID pola, ponieważ rozumie, że atrybut oznacza, że DatabaseGeneratedOption.None użytkownik powinien mieć możliwość wprowadzenia wartości klucza podstawowego. Nie rozumie jednak, że ponieważ liczba jest zrozumiała, chcesz ją zobaczyć w innych widokach, dlatego musisz dodać ją ręcznie.

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

<div class="form-group">
    @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DisplayFor(model => model.CourseID)
    </div>
</div>

Istnieje już ukryte pole (Html.HiddenFor pomocnik) dla numeru kursu w widoku Edycja. Dodanie pomocnika Html.LabelFor nie eliminuje potrzeby ukrytego pola, ponieważ nie powoduje dołączenia numeru kursu do opublikowanych danych, gdy użytkownik kliknie przycisk Zapisz na stronie Edytuj.

W obszarze Views\Course\Delete.cshtml i Views\Course\Details.cshtml zmień nazwę działu podpis z "Nazwa" na "Dział" i dodaj pole numeru kursu przed polem Tytuł.

<dt>
    Department
</dt>

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

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

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

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

Wartość Ustawienie
Liczba Wprowadź wartość 1000.
Tytuł Wprowadź algebra.
Środki Wprowadź wartość 4.
Dział Wybierz pozycję Matematyka.

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.

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

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

Dodawanie biura do strony 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 :

{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
    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)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    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, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.ID == id)
       .Single();

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

          db.SaveChanges();

          return RedirectToAction("Index");
       }
       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.");
      }
   }
   return View(instructorToUpdate);
}

Odwołanie wymaga RetryLimitExceededExceptionusing instrukcji , aby dodać ją — umieść kursor myszy na .RetryLimitExceededException Zostanie wyświetlony następujący komunikat:  Komunikat o wyjątku ponawiania próby

Wybierz pozycję Pokaż potencjalne poprawki, a następnie użyj pozycji System.Data.Entity.Infrastructure

Rozwiązywanie problemu z wyjątkiem ponawiania prób

Kod wykonuje następujące czynności:

  • Zmienia nazwę metody na EditPost , ponieważ podpis jest teraz taki sam jak HttpGet metoda ( ActionName atrybut określa, że /Edit/ URL jest nadal używany).

  • 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. Przeciążenie TryUpdateModel używane umożliwia wyświetlenie listy 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="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

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

Dodawanie kursów do strony instruktorów

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.

Relacja między jednostkami Course i Instructor jest wiele-do-wielu, co oznacza, że nie masz bezpośredniego dostępu do właściwości klucza obcego, które znajdują się w tabeli sprzężenia. Zamiast tego należy dodawać i usuwać 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 w celu utworzenia lub usunięcia relacji.

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 następującym kodem. Zmiany są wyróżnione.

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    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 ładowanie dla Courses 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 przypisano instruktora. 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. Zastąp metodę EditPost poniższym 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, string[] selectedCourses)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Include(i => i.Courses)
       .Where(i => i.ID == 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.SaveChanges();

            return RedirectToAction("Index");
        }
        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.");
        }
    }
    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);
         }
      }
   }
}

Sygnatura metody różni się teraz od HttpGetEdit metody, więc nazwa metody zmienia się z EditPost powrotem na Edit.

Ponieważ widok nie zawiera kolekcji Course jednostek, powiązanie modelu nie może automatycznie zaktualizować Courses właściwości nawigacji. Zamiast używać powiązania modelu do aktualizowania Courses właściwości nawigacji, 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 jawnego przeciążenia listy 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 w pętli 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 kod bezpośrednio po div elementach OfficeAssignment pola i przed div elementem przycisku Zapisz :

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <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>
</div>

Jeśli wklejasz kod, jeśli podziały wierszy i wcięcia nie wyglądają tak, jak w tym miejscu, ręcznie napraw wszystko, aby wyglądało to tak, jak widać tutaj. Wcięcie nie musi być idealne, ale @</tr><tr>wiersze , @:<td>, @:</td>i @</tr> muszą znajdować się w jednym wierszu, jak pokazano lub wystąpi błąd czasu wykonania.

Ten kod tworzy tabelę HTML zawierającą trzy kolumny. W każdej kolumnie jest pole wyboru, a następnie podpis, który składa 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 wybranych pól wyboru.

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

Po zmianie przypisań kursu warto sprawdzić zmiany po powrocie witryny 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>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
    <th></th> 
</tr>

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

<td>
    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
</td>
<td>
    @{
        foreach (var course in item.Courses)
        {
            @course.CourseID @:  @course.Title <br />
        }
    }
</td>
<td>
    @Html.ActionLink("Select", "Index", new { id = item.ID }) |
    @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
    @Html.ActionLink("Details", "Details", new { id = item.ID }) |
    @Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>

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

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

Zmień przydziały kursu i kliknij przycisk Zapisz. Wprowadzone zmiany zostaną odzwierciedlone na stronie Indeks.

Uwaga: Podejście podjęte tutaj 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 aktualizowania.

Aktualizowanie obiektu DeleteConfirmed

W pliku InstructorController.cs usuń metodę DeleteConfirmed i wstaw następujący kod w swoim miejscu.

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

   db.Instructors.Remove(instructor);

    var department = db.Departments
        .Where(d => d.InstructorID == id)
        .SingleOrDefault();
    if (department != null)
    {
        department.InstructorID = null;
    }

   db.SaveChanges();
   return RedirectToAction("Index");
}

Ten kod wprowadza następującą zmianę:

  • Jeśli instruktor jest przypisany jako administrator dowolnego działu, usuwa zadanie instruktora z tego działu. Bez tego kodu zostanie wyświetlony błąd integralności referencyjnej, jeśli próbowano usunąć instruktora, który został przypisany jako administrator działu.

Ten kod nie obsługuje scenariusza jednego instruktora przypisanego jako administrator dla wielu działów. W ostatnim samouczku dodasz kod, który uniemożliwia realizację tego scenariusza.

Dodawanie lokalizacji biura i kursów do strony Tworzenie

W pliku InstructorController.cs usuń HttpGet metody i HttpPostCreate , a następnie dodaj następujący kod w ich miejscu:

public ActionResult Create()
{
    var instructor = new Instructor();
    instructor.Courses = new List<Course>();
    PopulateAssignedCourseData(instructor);
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
    if (selectedCourses != null)
    {
        instructor.Courses = new List<Course>();
        foreach (var course in selectedCourses)
        {
            var courseToAdd = db.Courses.Find(int.Parse(course));
            instructor.Courses.Add(courseToAdd);
        }
    }
    if (ModelState.IsValid)
    {
        db.Instructors.Add(instructor);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

Ten kod jest podobny do tego, co zostało wyświetlone dla metod Edit, z tą różnicą, że początkowo nie wybrano żadnych kursów. Metoda HttpGetCreate wywołuje metodę PopulateAssignedCourseData nie dlatego, że mogą istnieć wybrane kursy, ale w celu udostępnienia pustej kolekcji dla foreach pętli w widoku (w przeciwnym razie kod widoku zgłosi wyjątek odwołania o wartości null).

Metoda HttpPost Create dodaje każdy wybrany kurs do właściwości nawigacji Courses przed kodem szablonu, który sprawdza błędy walidacji i dodaje nowego instruktora do bazy danych. Kursy są dodawane nawet wtedy, gdy występują błędy modelu, tak aby w przypadku wystąpienia błędów modelu (na przykład użytkownik wpisał nieprawidłową datę), tak aby po ponownym uruchomieniu strony z komunikatem o błędzie wszystkie dokonane wybory kursu zostały automatycznie przywrócone.

Zwróć uwagę, że aby można było dodać kursy do Courses właściwości nawigacji, musisz zainicjować właściwość jako pustą kolekcję:

instructor.Courses = new List<Course>();

Zamiast tego w kodzie kontrolera można to zrobić w modelu Instruktor, zmieniając metodę pobierania właściwości, aby automatycznie utworzyć kolekcję, jeśli nie istnieje, jak pokazano w poniższym przykładzie:

private ICollection<Course> _courses;
public virtual ICollection<Course> Courses 
{ 
    get
    {
        return _courses ?? (_courses = new List<Course>());
    }
    set
    {
        _courses = value;
    } 
}

Jeśli zmodyfikujesz Courses właściwość w ten sposób, możesz usunąć jawny kod inicjowania właściwości w kontrolerze.

W obszarze Views\Instructor\Create.cshtml dodaj pole tekstowe lokalizacji biura i pola wyboru kursu po polu daty zatrudnienia i przed przyciskiem Prześlij .

<div class="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <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>
</div>

Po wklejeniu kodu popraw podziały wierszy i wcięcie, tak jak wcześniej dla strony Edycja.

Uruchom stronę Tworzenie i dodaj instruktora.

Obsługa transakcji

Jak wyjaśniono w samouczku Podstawowe funkcje CRUD, domyślnie platforma Entity Framework niejawnie implementuje transakcje. W przypadku scenariuszy, w których potrzebujesz większej kontroli — na przykład jeśli chcesz uwzględnić operacje wykonywane poza programem Entity Framework w transakcji — zobacz Praca z transakcjami w witrynie MSDN.

Uzyskiwanie kodu

Pobieranie ukończonego projektu

Dodatkowe zasoby

Linki do innych zasobów programu Entity Framework można znaleźć w ASP.NET Dostęp do danych — zalecane zasoby.

Następny krok

W tym samouczku zostały wykonane następujące czynności:

  • Dostosowane strony kursów
  • Dodano biuro do strony instruktorów
  • Dodano kursy do strony instruktorów
  • Zaktualizowano polecenie DeleteConfirmed
  • Dodano lokalizację biura i kursy do strony Tworzenie

Przejdź do następnego artykułu, aby dowiedzieć się, jak zaimplementować asynchroniczny model programowania.