Actualización de datos relacionados con el Entity Framework en una aplicación ASP.NET MVC (6 de 10)

por Tom Dykstra

En la aplicación Web de ejemplo contoso University se muestra cómo crear aplicaciones ASP.NET MVC 4 con el Code First de Entity Framework 5 y Visual Studio 2012. Para obtener información sobre la serie de tutoriales, consulte el primer tutorial de la serie.

Note

Si experimenta un problema que no puede resolver, Descargue el capítulo completado e intente reproducir el problema. Por lo general, puede encontrar la solución al problema comparando el código con el código completado. Para algunos errores comunes y cómo resolverlos, consulte errores y soluciones alternativas.

En el tutorial anterior se muestran datos relacionados. en este tutorial, actualizará los datos relacionados. Para la mayoría de las relaciones, esto se puede hacer actualizando los campos de clave externa adecuados. En el caso de las relaciones de varios a varios, el Entity Framework no expone directamente la tabla de combinación, por lo que debe agregar y quitar explícitamente entidades de las propiedades de navegación adecuadas.

En las ilustraciones siguientes se muestran las páginas con las que va a trabajar.

Course_create_page

Instructor_edit_page_with_courses

Personalizar las páginas Create y Edit de Courses

Cuando se crea una entidad de curso, debe tener una relación con un departamento existente. Para facilitar esto, el código con scaffolding incluye métodos de controlador y vistas de Create y Edit que incluyen una lista desplegable para seleccionar el departamento. La lista desplegable establece la Course.DepartmentID propiedad de clave externa y eso es todo lo que necesita Entity Framework para cargar la propiedad de Department navegación con la entidad adecuada Department . Podrá usar el código con scaffolding, pero cámbielo ligeramente para agregar el control de errores y ordenar la lista desplegable.

En CourseController.CS, elimine los Edit cuatro Create métodos y y reemplácelos por el código siguiente:

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

El PopulateDepartmentsDropDownList método obtiene una lista de todos los departamentos ordenados por nombre, crea SelectList una colección para una lista desplegable y pasa la colección a la vista de una ViewBag propiedad. El método acepta el parámetro opcional selectedDepartment, que permite al código que realiza la llamada especificar el elemento que se seleccionará cuando se procese la lista desplegable. La vista pasará el nombre DepartmentID al DropDownList ayudantey, a continuación, el ayudante sabe que debe buscar en el ViewBag objeto un SelectList denominado DepartmentID .

El HttpGet Create método llama al PopulateDepartmentsDropDownList método sin establecer el elemento seleccionado porque, para un nuevo curso, el Departamento no se ha establecido todavía:

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

El HttpGet Edit método establece el elemento seleccionado, en función del identificador del Departamento que ya está asignado al curso que se está editando:

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

Los HttpPost métodos para Create y Edit también incluyen código que establece el elemento seleccionado cuando vuelve a mostrar la página después de un error:

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

Este código garantiza que cuando se vuelva a mostrar la página para mostrar el mensaje de error, el Departamento que se haya seleccionado permanecerá seleccionado.

En Views\Course\Create.cshtml, agregue el código resaltado para crear un nuevo campo de número de curso antes del campo de título . Como se explicó en un tutorial anterior, los campos de clave principal no tienen scaffolding de forma predeterminada, pero esta clave principal es significativa, por lo que desea que el usuario pueda escribir el valor de la clave.

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

En Views\Course\Edit.cshtml, Views\Course\Delete.cshtml y Views\Course\Details.cshtml, agregue un campo de número de curso antes del campo de título . Dado que es la clave principal, se muestra, pero no se puede cambiar.

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

Ejecute la página crear (muestre la página de índice del curso y haga clic en crear nuevo) y escriba los datos de un nuevo curso:

Course_create_page

Haga clic en Crear. La página de índice del curso se muestra con el nuevo curso agregado a la lista. El nombre de departamento de la lista de páginas de índice proviene de la propiedad de navegación, que muestra que la relación se estableció correctamente.

Course_Index_page_showing_new_course

Ejecute la página Editar (muestre la página de índice del curso y haga clic en Editar en un curso).

Course_edit_page

Cambie los datos en la página y haga clic en Save. La página de índice del curso se muestra con los datos del curso actualizados.

Agregar una página de edición para los instructores

Al editar un registro de instructor, necesita poder actualizar la asignación de la oficina del instructor. La Instructor entidad tiene una relación de uno a cero o uno con la OfficeAssignment entidad, lo que significa que debe administrar las siguientes situaciones:

  • Si el usuario borra la asignación de la oficina y originalmente tenía un valor, debe quitar y eliminar la OfficeAssignment entidad.
  • Si el usuario escribe un valor de asignación de la oficina y originalmente estaba vacío, debe crear una nueva OfficeAssignment entidad.
  • Si el usuario cambia el valor de una asignación de Office, debe cambiar el valor de una OfficeAssignment entidad existente.

Abra InstructorController.CS y examine el HttpGet Edit método:

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

El código con scaffolding no es lo que desea. Está configurando los datos de una lista desplegable, pero lo que necesita es un cuadro de texto. Reemplace este método por el código siguiente:

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

Este código quita la ViewBag instrucción y agrega la carga diligente para la OfficeAssignment entidad asociada. No se puede realizar la carga diligente con el Find método, por lo que Where Single se usan los métodos y en su lugar para seleccionar el instructor.

Reemplace el HttpPost Edit método por el código siguiente. que controla las actualizaciones de asignación de 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);
}

El código realiza lo siguiente:

  • Obtiene la entidad Instructor actual de la base de datos mediante la carga diligente de la propiedad de navegación OfficeAssignment. Esto es lo mismo que hizo en el HttpGet Edit método.

  • Actualiza la entidad Instructor recuperada con valores del enlazador de modelos. La sobrecarga de TryUpdateModel utilizada le permite incluir en la lista blanca las propiedades que desea incluir. Esto evita el exceso de registro, como se explica en el segundo tutorial.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Si la ubicación de la oficina está en blanco, establece la Instructor.OfficeAssignment propiedad en null para que se elimine la fila relacionada de la OfficeAssignment tabla.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Guarda los cambios en la base de datos.

En Views\Instructor\Edit.cshtml, después de los div elementos del campo de fecha de contratación , agregue un nuevo campo para editar la ubicación de la oficina:

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

Ejecute la página (seleccione la pestaña instructores y, a continuación, haga clic en Editar en un instructor). Cambie el valor de Office Location y haga clic en Save.

Changing_the_office_location

Agregar asignaciones de cursos a la página de edición del instructor

Los instructores pueden impartir cualquier número de cursos. Ahora mejorará la página Edit Instructor al agregar la capacidad de cambiar las asignaciones de cursos mediante un grupo de casillas, tal y como se muestra en la siguiente captura de pantalla:

Instructor_edit_page_with_courses

La relación entre las Course Instructor entidades y es de varios a varios, lo que significa que no tiene acceso directo a la tabla de combinación. En su lugar, agregará y quitará entidades de la Instructor.Courses propiedad de navegación.

La interfaz de usuario que le permite cambiar los cursos a los que está asignado un instructor es un grupo de casillas. Se muestra una casilla para cada curso en la base de datos y se seleccionan aquellos a los que está asignado actualmente el instructor. El usuario puede activar o desactivar las casillas para cambiar las asignaciones de cursos. Si el número de cursos era mucho mayor, probablemente desee usar un método diferente para presentar los datos en la vista, pero usaría el mismo método para manipular las propiedades de navegación con el fin de crear o eliminar relaciones.

Para proporcionar datos a la vista de la lista de casillas, deberá usar una clase de modelo de vista. Cree AssignedCourseData.CS en la carpeta ViewModels y reemplace el código existente por el código siguiente:

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

En InstructorController.CS, reemplace el HttpGet Edit método por el código siguiente. Los cambios aparecen resaltados.

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

El código agrega carga diligente para la propiedad de navegación Courses y llama al método PopulateAssignedCourseData nuevo para proporcionar información de la matriz de casilla mediante la clase de modelo de vista AssignedCourseData.

El código del PopulateAssignedCourseData método lee todas las Course entidades para cargar una lista de cursos mediante la clase de modelo de vista. Para cada curso, el código comprueba si existe el curso en la propiedad de navegación Courses del instructor. Para crear una búsqueda eficaz al comprobar si un curso está asignado al instructor, los cursos asignados al instructor se colocan en una colección HashSet . La Assigned propiedad se establece en true para los cursos a los que se asigna el instructor. La vista usará esta propiedad para determinar qué casilla debe mostrarse como seleccionada. Por último, la lista se pasa a la vista en una ViewBag propiedad.

A continuación, agregue el código que se ejecuta cuando el usuario hace clic en Save. Reemplace el HttpPost Edit método por el código siguiente, que llama a un nuevo método que actualiza la Courses propiedad de navegación de la Instructor entidad. Los cambios aparecen resaltados.

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

Dado que la vista no tiene una colección de Course entidades, el enlazador de modelos no puede actualizar automáticamente la Courses propiedad de navegación. En lugar de usar el enlazador de modelos para actualizar la propiedad de navegación Courses, lo hará en el nuevo UpdateInstructorCourses método. Por lo tanto, tendrá que excluir la propiedad Courses del enlace de modelos. Esto no requiere ningún cambio en el código que llama a TryUpdateModel porque se usa la sobrecarga de la lista de permitidos y no se encuentra Courses en la lista de inclusión.

Si no se ha seleccionado ninguna casilla, el código de UpdateInstructorCourses inicializa la Courses propiedad de navegación con una colección vacía:

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

A continuación, el código recorre en bucle todos los cursos de la base de datos y coteja los que están asignados actualmente al instructor frente a los que se han seleccionado en la vista. Para facilitar las búsquedas eficaces, estas dos últimas colecciones se almacenan en objetos HashSet.

Si se ha activado la casilla para un curso pero este no se encuentra en la propiedad de navegación Instructor.Courses, el curso se agrega a la colección en la propiedad de navegación.

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

Si no se ha activado la casilla para un curso pero este se encuentra en la propiedad de navegación Instructor.Courses, el curso se quita de la colección en la propiedad de navegación.

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

En Views\Instructor\Edit.cshtml, agregue un campo Courses con una matriz de casillas agregando el siguiente código resaltado inmediatamente después de los div elementos del OfficeAssignment campo:

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

Este código crea una tabla HTML que tiene tres columnas. En cada columna hay una casilla seguida de un título que está formado por el número y el título del curso. Todas las casillas tienen el mismo nombre ("selectedCourses"), que informa al enlazador de modelos que se va a tratar como un grupo. El value atributo de cada casilla se establece en el valor de CourseID. cuando se envía la página, el enlazador de modelos pasa una matriz al controlador que consta únicamente de los CourseID valores de las casillas seleccionadas.

Cuando se representan inicialmente las casillas, las que se aplican a los cursos asignados al instructor tienen checked atributos, que las selecciona (las muestra activadas).

Después de cambiar las asignaciones de cursos, querrá comprobar los cambios cuando el sitio vuelva a la Index página. Por lo tanto, debe agregar una columna a la tabla en esa página. En este caso, no es necesario usar el ViewBag objeto, porque la información que desea mostrar ya está en la Courses propiedad de navegación de la Instructor entidad que va a pasar a la página como modelo.

En Views\Instructor\Index.cshtml, agregue un encabezado Courses inmediatamente después del título de Office , tal como se muestra en el ejemplo siguiente:

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

Después, agregue una nueva celda de detalle inmediatamente después de la celda de detalle de la ubicación de la oficina:

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

Ejecute la página de Índice del instructor para ver los cursos asignados a cada instructor:

Instructor_index_page

Haga clic en Editar en un instructor para ver la página de edición.

Instructor_edit_page_with_courses

Cambie algunas asignaciones de cursos y haga clic en Guardar. Los cambios que haga se reflejan en la página de índice.

Nota: el enfoque que se lleva a cabo para editar los datos del curso del instructor funciona bien cuando hay un número limitado de cursos. Para las colecciones que son mucho más grandes, se necesitaría una interfaz de usuario y un método de actualización diferentes.

Actualización del método Delete

Cambie el código del método HttpPost delete para que el registro de asignación de Office (si existe) se elimine cuando se elimine el instructor:

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

Si intenta eliminar un instructor asignado a un departamento como administrador, obtendrá un error de integridad referencial. Vea la versión actual de este tutorial para obtener código adicional que quitará automáticamente el instructor de cualquier departamento en el que el instructor esté asignado como administrador.

Resumen

Ya ha completado esta introducción al trabajo con datos relacionados. Hasta ahora en estos tutoriales ha realizado una gama completa de operaciones CRUD, pero no ha abordado los problemas de simultaneidad. En el siguiente tutorial se presenta el tema de simultaneidad, se explican las opciones para controlarlo y se agrega el control de simultaneidad al código CRUD que ya se ha escrito para un tipo de entidad.

Los vínculos a otros recursos de Entity Framework, se pueden encontrar al final del último tutorial de esta serie.