Atualizando dados relacionados com o Entity Framework em um aplicativo MVC ASP.NET (6 de 10)

por Tom Dykstra

O aplicativo Web de exemplo da Contoso University demonstra como criar aplicativos ASP.NET MVC 4 usando o Entity Framework 5 Code First e o Visual Studio 2012. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial da série.

Note

Se você encontrar um problema que não possa resolver, Baixe o capítulo concluído e tente reproduzir o problema. Em geral, você pode encontrar a solução para o problema comparando seu código com o código concluído. Para alguns erros comuns e como resolvê-los, consulte erros e soluções alternativas.

No tutorial anterior, você exibiu dados relacionados; Neste tutorial, você atualizará os dados relacionados. Para a maioria das relações, isso pode ser feito atualizando os campos de chave estrangeira apropriados. Para relações muitos para muitos, o Entity Framework não expõe a tabela de junção diretamente, portanto, você deve adicionar e remover explicitamente entidades de e para as propriedades de navegação apropriadas.

As ilustrações a seguir mostram as páginas com as quais você trabalhará.

Course_create_page

Instructor_edit_page_with_courses

Personalizar as páginas Criar e Editar dos cursos

Quando uma nova entidade de curso é criada, ela precisa ter uma relação com um departamento existente. Para facilitar isso, o código gerado por scaffolding inclui métodos do controlador e exibições Criar e Editar que incluem uma lista suspensa para seleção do departamento. A lista suspensa define a propriedade de Course.DepartmentID chave estrangeira, e isso é tudo o Entity Framework precisa para carregar a Department propriedade de navegação com a Department entidade apropriada. Você usará o código gerado por scaffolding, mas o alterará ligeiramente para adicionar tratamento de erro e classificação à lista suspensa.

No CourseController.cs, exclua os Edit quatro Create métodos e e substitua-os pelo seguinte código:

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

O PopulateDepartmentsDropDownList método obtém uma lista de todos os departamentos classificados por nome, cria uma SelectList coleção para uma lista suspensa e passa a coleção para a exibição em uma ViewBag propriedade. O método aceita o parâmetro selectedDepartment opcional que permite que o código de chamada especifique o item que será selecionado quando a lista suspensa for renderizada. A exibição passará o nome DepartmentID para o DropDownList auxiliare, em seguida, o auxiliar saberá examinar o ViewBag objeto em busca de um SelectList nome DepartmentID .

O HttpGet Create método chama o PopulateDepartmentsDropDownList método sem definir o item selecionado, porque, para um novo curso, o departamento ainda não foi estabelecido:

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

O HttpGet Edit método define o item selecionado, com base na ID do departamento que já está atribuído ao curso que está sendo editado:

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

Os HttpPost métodos para ambos Create e Edit também incluem código que define o item selecionado quando eles reexibem a página após um erro:

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

Esse código garante que quando a página for exibida novamente para mostrar a mensagem de erro, qualquer departamento selecionado permanecerá selecionado.

No Views\Course\Create.cshtml, adicione o código realçado para criar um novo campo de número de curso antes do campo título . Conforme explicado em um tutorial anterior, os campos de chave primária não são com Scaffold por padrão, mas essa chave primária é significativa, portanto, você deseja que o usuário seja capaz de inserir o valor da chave.

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

Em Views\Course\Edit.cshtml, Views\Course\Delete.cshtml e Views\Course\Details.cshtml, adicione um campo de número de curso antes do campo título . Como é a chave primária, ela é exibida, mas não pode ser alterada.

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

Execute a página criar (exibir a página de índice do curso e clique em criar nova) e insira dados para um novo curso:

Course_create_page

Clique em Criar. A página de índice do curso é exibida com o novo curso adicionado à lista. O nome do departamento na lista de páginas de Índice é obtido da propriedade de navegação, mostrando que a relação foi estabelecida corretamente.

Course_Index_page_showing_new_course

Execute a página Editar (exiba a página de índice do curso e clique em Editar em um curso).

Course_edit_page

Altere dados na página e clique em Salvar. A página de índice do curso é exibida com os dados do curso atualizados.

Adicionando uma página de edição para instrutores

Quando você edita um registro de instrutor, deseja poder atualizar a atribuição de escritório do instrutor. A Instructor entidade tem uma relação um-para-zero-ou-um com a OfficeAssignment entidade, o que significa que você deve lidar com as seguintes situações:

  • Se o usuário desmarcar a atribuição do Office e tiver originalmente um valor, você deverá remover e excluir a OfficeAssignment entidade.
  • Se o usuário inserir um valor de atribuição do Office e ele tiver sido originalmente vazio, você deverá criar uma nova OfficeAssignment entidade.
  • Se o usuário alterar o valor de uma atribuição do Office, você deverá alterar o valor em uma OfficeAssignment entidade existente.

Abra InstructorController.cs e examine o 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);
}

O código com Scaffold aqui não é o que você deseja. Ele está configurando dados para uma lista suspensa, mas você precisa de uma caixa de texto. Substitua este método pelo código a seguir:

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

Esse código descarta a ViewBag instrução e adiciona o carregamento adiantado para a OfficeAssignment entidade associada. Você não pode executar o carregamento rápido com o Find método, portanto, os Where Single métodos e são usados para selecionar o instrutor.

Substitua o HttpPost Edit método pelo código a seguir. que lida com atualizações de atribuição do 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);
}

O código faz o seguinte:

  • Obtém a entidade Instructor atual do banco de dados usando o carregamento adiantado para a propriedade de navegação OfficeAssignment. Isso é o mesmo que você fez no HttpGet Edit método.

  • Atualiza a entidade Instructor recuperada com valores do associador de modelos. A sobrecarga TryUpdateModel usada permite que você acesse as propriedades que deseja incluir na lista de permissões. Isso impede o excesso de postagens, conforme explicado no segundo tutorial.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Se o local do escritório estiver em branco, o definirá a Instructor.OfficeAssignment propriedade como nula para que a linha relacionada na OfficeAssignment tabela seja excluída.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Salva as alterações no banco de dados.

Em Views\Instructor\Edit.cshtml, depois dos div elementos do campo data de contratação , adicione um novo campo para editar o local do escritório:

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

Execute a página (selecione a guia instrutores e clique em Editar em um instrutor). Altere o Local do Escritório e clique em Salvar.

Changing_the_office_location

Adicionando atribuições de curso à página de edição do instrutor

Os instrutores podem ministrar a quantidade de cursos que desejarem. Agora, você aprimorará a página Editar Instrutor adicionando a capacidade de alterar as atribuições de curso usando um grupo de caixas de seleção, conforme mostrado na seguinte captura de tela:

Instructor_edit_page_with_courses

A relação entre as Course Instructor entidades e é muitos para muitos, o que significa que você não tem acesso direto à tabela de junção. Em vez disso, você adicionará e removerá entidades de e para a Instructor.Courses propriedade de navegação.

A interface do usuário que permite alterar a quais cursos um instrutor é atribuído é um grupo de caixas de seleção. Uma caixa de seleção é exibida para cada curso no banco de dados, e aqueles aos quais o instrutor está atribuído no momento são marcados. O usuário pode marcar ou desmarcar as caixas de seleção para alterar as atribuições de curso. Se o número de cursos fosse muito maior, provavelmente você desejaria usar um método diferente de apresentar os dados na exibição, mas usará o mesmo método de manipulação de propriedades de navegação para criar ou excluir relações.

Para fornecer dados à exibição para a lista de caixas de seleção, você usará uma classe de modelo de exibição. Crie AssignedCourseData.cs na pasta ViewModels e substitua o código existente pelo código a seguir:

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

No InstructorController.cs, substitua o HttpGet Edit método pelo código a seguir. As alterações são realçadas.

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

O código adiciona o carregamento adiantado à propriedade de navegação Courses e chama o novo método PopulateAssignedCourseData para fornecer informações para a matriz de caixa de seleção usando a classe de modelo de exibição AssignedCourseData.

O código no PopulateAssignedCourseData método lê todas as Course entidades para carregar uma lista de cursos usando a classe de modelo de exibição. Para cada curso, o código verifica se o curso existe na propriedade de navegação Courses do instrutor. Para criar uma pesquisa eficiente ao verificar se um curso está atribuído ao instrutor, os cursos atribuídos ao instrutor são colocados em uma coleção HashSet . A Assigned propriedade é definida como true para os cursos que o instrutor está atribuído. A exibição usará essa propriedade para determinar quais caixas de seleção precisam ser exibidas como selecionadas. Por fim, a lista é passada para a exibição em uma ViewBag propriedade.

Em seguida, adicione o código que é executado quando o usuário clica em Salvar. Substitua o HttpPost Edit método pelo código a seguir, que chama um novo método que atualiza a Courses propriedade de navegação da Instructor entidade. As alterações são realçadas.

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

Como a exibição não tem uma coleção de Course entidades, o associador de modelo não pode atualizar automaticamente a Courses propriedade de navegação. Em vez de usar o associador de modelo para atualizar a propriedade de navegação de cursos, você fará isso no novo UpdateInstructorCourses método. Portanto, você precisa excluir a propriedade Courses do model binding. Isso não requer nenhuma alteração no código que chama TryUpdateModel porque você está usando a sobrecarga de lista de permissões e Courses não está na lista de inclusões.

Se nenhuma caixa de seleção tiver sido selecionada, o código no UpdateInstructorCourses inicializará a Courses propriedade de navegação com uma coleção vazia:

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

Em seguida, o código executa um loop em todos os cursos no banco de dados e verifica cada curso em relação àqueles atribuídos no momento ao instrutor e em relação àqueles que foram selecionados na exibição. Para facilitar pesquisas eficientes, as últimas duas coleções são armazenadas em objetos HashSet.

Se a caixa de seleção para um curso foi marcada, mas o curso não está na propriedade de navegação Instructor.Courses, o curso é adicionado à coleção na propriedade de navegação.

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

Se a caixa de seleção para um curso não foi marcada, mas o curso está na propriedade de navegação Instructor.Courses, o curso é removido da propriedade de navegação.

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

No Views\Instructor\Edit.cshtml, adicione um campo cursos com uma matriz de caixas de seleção adicionando o seguinte código realçado imediatamente após os div elementos para o 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")
}

Esse código cria uma tabela HTML que contém três colunas. Em cada coluna há uma caixa de seleção, seguida de uma legenda que consiste no número e título do curso. Todas as caixas de seleção têm o mesmo nome ("selectedCourses"), que informa ao associador de modelo que eles devem ser tratados como um grupo. O value atributo de cada caixa de seleção é definido como o valor de CourseID. quando a página é postada, o associador de modelo passa uma matriz para o controlador que consiste nos CourseID valores somente para as caixas de seleção que estão selecionadas.

Quando as caixas de seleção são inicialmente renderizadas, as que são para os cursos atribuídos ao instrutor têm checked atributos, que os seleciona (exibe as marcações).

Depois de alterar as atribuições de curso, você desejará poder verificar as alterações quando o site retornar à Index página. Portanto, você precisa adicionar uma coluna à tabela nessa página. Nesse caso, você não precisa usar o ViewBag objeto, pois as informações que você deseja exibir já estão na Courses propriedade de navegação da Instructor entidade que você está passando para a página como modelo.

No Views\Instructor\Index.cshtml, adicione um título de cursos imediatamente após o título do Office , conforme mostrado no exemplo a seguir:

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

Em seguida, adicione uma nova célula de detalhes imediatamente após a célula de detalhes do local do escritório:

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

Execute a página de índice do instrutor para ver os cursos atribuídos a cada instrutor:

Instructor_index_page

Clique em Editar em um instrutor para ver a página Editar.

Instructor_edit_page_with_courses

Altere algumas atribuições de curso e clique em salvar. As alterações feitas são refletidas na página Índice.

Observação: a abordagem usada para editar os dados do curso do instrutor funciona bem quando há um número limitado de cursos. Para coleções muito maiores, uma interface do usuário e um método de atualização diferentes são necessários.

Atualizar o método Delete

Altere o código no método de exclusão HttpPost para que o registro de atribuição do Office (se houver) seja excluído quando o instrutor for excluído:

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

Se você tentar excluir um instrutor atribuído a um departamento como administrador, obterá um erro de integridade referencial. Consulte a versão atual deste tutorial para obter código adicional que removerá automaticamente o instrutor de qualquer departamento em que o instrutor seja atribuído como um administrador.

Resumo

Agora você concluiu esta introdução para trabalhar com dados relacionados. Até agora, nesses tutoriais, você fez uma gama completa de operações CRUD, mas não tratou de problemas de simultaneidade. O próximo tutorial apresentará o tópico de simultaneidade, explicará as opções para tratá-lo e adicionará a manipulação de simultaneidade ao código CRUD que você já escreveu para um tipo de entidade.

Links para outros recursos do Entity Framework, podem ser encontrados no final do último tutorial desta série.