Tutorial: atualizar dados relacionados com o EF em um aplicativo MVC ASP.NET

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 ou as propriedades de navegação. Para relações muitos para muitos, o Entity Framework não expõe a tabela de junção diretamente, portanto, você adiciona e remove entidades de e para as propriedades de navegação apropriadas.

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

Course_create_page

Instructor_edit_page_with_courses

Edição do instrutor com cursos

Neste tutorial, você:

  • Personalizar páginas de cursos
  • Página Adicionar o Office à instrutor
  • Página Adicionar cursos a instrutores
  • Atualizar DeleteConfirmed
  • Adicionar local de escritório e cursos para a página Criar

Prerequisites

Personalizar páginas de 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 chave estrangeira Course.DepartmentID, e isso é tudo o que Entity Framework precisa para carregar a propriedade de navegação Department com a entidade Department 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 quatro métodos Create e Edit e substitua-os pelo código a seguir:

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

Adicione a seguinte instrução de using no início do arquivo:

using System.Data.Entity.Infrastructure;

O método PopulateDepartmentsDropDownList Obtém uma lista de todos os departamentos classificados por nome, cria uma coleção de SelectList para uma lista suspensa e passa a coleção para a exibição em uma propriedade ViewBag. 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 auxiliar DropDownList , e o auxiliar saberá procurar no objeto ViewBag para um SelectList chamado DepartmentID.

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

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

O método HttpGet Edit 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)
{
    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);
}

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

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

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

As exibições do curso já estão com scaffolddas com listas suspensas para o campo departamento, mas você não quer a legenda DepartmentID para esse campo, portanto, faça a seguinte alteração realçada no arquivo Views\Course\Create.cshtml para alterar a legenda.

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

Faça a mesma alteração em Views\Course\Edit.cshtml.

Normalmente, o scaffolder não Scaffold uma chave primária porque o valor da chave é gerado pelo banco de dados e não pode ser alterado e não é um valor significativo a ser exibido aos usuários. Para entidades de curso, o scaffolder inclui uma caixa de texto para o campo CourseID porque ele entende que o atributo DatabaseGeneratedOption.None significa que o usuário deve ser capaz de inserir o valor da chave primária. Mas não entende que, como o número é significativo, você deseja vê-lo nas outras exibições, portanto, você precisa adicioná-lo manualmente.

Em Views\Course\Edit.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="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>

Já existe um campo oculto (Html.HiddenFor auxiliar) para o número do curso no modo de exibição de edição. Adicionar um auxiliar HTML. LabelFor não elimina a necessidade do campo oculto porque ele não faz com que o número do curso seja incluído nos dados postados quando o usuário clica em salvar na página Editar.

Em Views\Course\Delete.cshtml e Views\Course\Details.cshtml, altere a legenda do nome do departamento de "nome" para "departamento" e adicione um campo de número do curso antes do campo título .

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

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:

Valor Configuração
Número Insira 1000.
Title Digite Algebra.
Credits Insira 4.
department Selecione matemática.

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.

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

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

Página Adicionar o Office à instrutor

Quando você edita um registro de instrutor, deseja poder atualizar a atribuição de escritório do instrutor. A entidade Instructor tem uma relação um-para-zero-ou-um com a entidade OfficeAssignment, 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 entidade OfficeAssignment.
  • Se o usuário inserir um valor de atribuição do Office e ele tiver sido originalmente vazio, você deverá criar uma nova entidade de OfficeAssignment.
  • Se o usuário alterar o valor de uma atribuição do Office, você deverá alterar o valor em uma entidade de OfficeAssignment existente.

Abra InstructorController.cs e examine o método de Edit HttpGet:

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

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

Esse código descarta a instrução ViewBag e adiciona o carregamento adiantado para a entidade OfficeAssignment associada. Não é possível executar o carregamento adiantado com o método Find, para que os métodos Where e Single sejam usados em vez de selecionar o instrutor.

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

A referência a RetryLimitExceededException requer uma instrução using; para adicioná-lo, passe o mouse sobre RetryLimitExceededException. A seguinte mensagem é exibida:  mensagem de exceção de repetição

Selecione Mostrar possíveis correçõese, em seguida, usando System. Data. Entity. Infrastructure

Resolver exceção de repetição

O código faz o seguinte:

  • Altera o nome do método para EditPost porque a assinatura agora é a mesma do método HttpGet (o atributo ActionName especifica que a URL/Edit/ainda é usada).

  • 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 método de Edit HttpGet.

  • 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 propriedade Instructor.OfficeAssignment como nula, de modo que a linha relacionada na tabela OfficeAssignment será excluída.

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

No Views\Instructor\Edit.cshtml, após os elementos de div para o campo data de contratação , adicione um novo campo para editar o local do escritório:

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

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.

Página Adicionar cursos a instrutores

Os instrutores podem ministrar a quantidade de cursos que desejarem. Agora você aprimorará a página de edição do instrutor adicionando a capacidade de alterar as atribuições de curso usando um grupo de caixas de seleção.

A relação entre as entidades Course e Instructor é muitos para muitos, o que significa que você não tem acesso direto às propriedades de chave estrangeira que estão na tabela de junção. Em vez disso, você adiciona e remove entidades de e para a propriedade de navegação Instructor.Courses.

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 método HttpGet Edit pelo código a seguir. As alterações são realçadas.

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();
    PopulateAssignedCourseData(instructor);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    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 método PopulateAssignedCourseData lê todas as entidades de Course 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 propriedade Assigned é definida como true para 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 propriedade ViewBag.

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

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

A assinatura do método agora é diferente do método HttpGet Edit, portanto, o nome do método muda de EditPost de volta para Edit.

Como a exibição não tem uma coleção de entidades de Course, o associador de modelo não pode atualizar automaticamente a propriedade de navegação Courses. Em vez de usar o associador de modelo para atualizar a propriedade de navegação Courses, você fará isso no novo método UpdateInstructorCourses. 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 em UpdateInstructorCourses inicializará a propriedade de navegação Courses 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 de cursos com uma matriz de caixas de seleção adicionando o código a seguir imediatamente após os elementos de div para o campo OfficeAssignment e antes do elemento div para o botão salvar :

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

Depois de colar o código, se as quebras de linha e o recuo não parecerem com eles, corrija manualmente tudo para que ele se pareça com o que você vê aqui. O recuo não precisa ser perfeito, mas cada uma das linhas @</tr><tr>, @:<td>, @:</td> e @</tr> precisa estar em uma única linha, conforme mostrado, ou você receberá um erro de runtime.

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 atributo value 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 valores de CourseID apenas para as caixas de seleção que estão selecionadas.

Quando as caixas de seleção são processadas inicialmente, as que são para cursos atribuídos ao instrutor têm checked atributos, o 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 à página Index. Portanto, você precisa adicionar uma coluna à tabela nessa página. Nesse caso, não é necessário usar o objeto ViewBag, porque as informações que você deseja exibir já estão na propriedade de navegação Courses da entidade Instructor 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>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
    <th></th> 
</tr>

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

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

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

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

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

No InstructorController.cs, exclua o método DeleteConfirmed e insira o código a seguir em seu lugar.

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

Esse código faz a seguinte alteração:

  • Se o instrutor for atribuído como administrador de qualquer departamento, o removerá a atribuição do instrutor desse departamento. Sem esse código, você obterá um erro de integridade referencial se tentar excluir um instrutor que foi atribuído como administrador de um departamento.

Esse código não lida com o cenário de um instrutor atribuído como administrador para vários departamentos. No último tutorial, você adicionará um código que impede que esse cenário aconteça.

Adicionar local de escritório e cursos para a página Criar

No InstructorController.cs, exclua os métodos HttpGet e HttpPost Create e, em seguida, adicione o seguinte código em seu lugar:

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

Esse código é semelhante ao que você viu para os métodos de edição, exceto que inicialmente nenhum curso está selecionado. O método Create HttpGet chama o método PopulateAssignedCourseData não porque pode haver cursos selecionados, mas para fornecer uma coleção vazia para o loop de foreach na exibição (caso contrário, o código de exibição geraria uma exceção de referência nula).

O método HttpPost Create adiciona cada curso selecionado à propriedade de navegação de cursos antes do código de modelo que verifica erros de validação e adiciona o novo instrutor ao banco de dados. Os cursos são adicionados mesmo se houver erros de modelo para que, quando houver erros de modelo (por exemplo, o usuário tenha inserido uma data inválida) para que, quando a página for exibida novamente com uma mensagem de erro, quaisquer seleções de curso feitas sejam automaticamente restauradas.

Observe que para poder adicionar cursos à propriedade de navegação Courses, é necessário inicializar a propriedade como uma coleção vazia:

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

Como alternativa a fazer isso no código do controlador, faça isso no modelo Instrutor alterando o getter de propriedade para criar automaticamente a coleção se ela não existir, conforme mostrado no seguinte exemplo:

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

Se você modificar a propriedade Courses dessa forma, poderá remover o código de inicialização de propriedade explícita no controlador.

No Views\Instructor\Create.cshtml, adicione uma caixa de texto de localização do Office e caixas de seleção do curso após o campo data de contratação e antes do botão Enviar .

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

Depois de colar o código, corrija as quebras de linha e o recuo como você fez anteriormente para a página de edição.

Execute a página criar e adicione um instrutor.

Tratamento de transações

Conforme explicado no tutorial básico de funcionalidade CRUD, por padrão, o Entity Framework implementa as transações implicitamente. Para cenários em que você precisa de mais controle – por exemplo, se você quiser incluir operações feitas fora do Entity Framework em uma transação--consulte trabalhando com transações no msdn.

Obter o código

Baixar o projeto concluído

Recursos adicionais

Links para outros recursos de Entity Framework podem ser encontrados em recursos de acesso a dados ASP.net-recomendado.

Próxima etapa

Neste tutorial, você:

  • Páginas de cursos personalizados
  • Página do Office para instrutores adicionada
  • Página de cursos adicionados aos instrutores
  • DeleteConfirmed atualizado
  • Local e cursos do Office adicionados à página criar

Avance para o próximo artigo para aprender a implementar um modelo de programação assíncrona.