ASP .NET MVC - Atualizando dados relacionados com Entity Framework - VI
Este artigo é baseado no original: Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10) com adaptações e pequenos ajustes feitos por mim.
Antes de prosseguir verifique se você possui os seguintes recursos instalados:
Esta é sexta parte do artigo, e, se você esta chegando agora deve ler obrigatoriamente as partes anteriores:
Neste artigo vamos mostrar como atualizar dados relacionados com Entity Framework. Para muitos relacionamentos a atualização pode ser feita usando o apropriado campo chave-estrangeira. Para relacionamentos muitos-para-muitos o Entity Framework não expõe diretamente a tabela join, de forma que precisamos adicionar e remover entidades de e a partir das devidas propriedades de navegação.
isto é, os dados que o Entity Framework através das propriedades de navegação.
A partir deste tutorial eu estou usando o Visual Studio 2012 Express For web para abrir e criar os projetos.
Customizando as páginas para Criar e Editar Cursos
Quando uma nova entidade curso é criada, ela deve ter uma relação com um departamento existente. Para facilitar este processo, o código gerado inclui métodos do controlador e as views para Criar e Editar que incluem uma lista dropdown para selecionar o departamento.
A lista drop-down define a propriedade de chave estrangeira Curso.DepartamentoID, e isso é tudo que o Entity Framework precisa para carregar a propriedade de navegação Departamento com a entidade departamento apropriada. Vamos usar o código gerado alterando-o um pouco para adicionar o tratamento de erros e classificar a lista dropdown.
Vamos abrir o arquivo CursoController da pasta Controllers e excluir os métodos Edit e Create substituindo-os pelo código abaixo:
... ' GET: /Course/Create Function Create() As ViewResult PopularDepartamentosDropDownList() Return View() End Function ' POST: /Course/Create <HttpPost()> Function Create(_curso As Curso) As ActionResult Try If ModelState.IsValid Then db.Cursos.Add(_curso) db.SaveChanges() Return RedirectToAction("Index") End If Catch ex As DataException 'Log the error ModelState.AddModelError("", "Não foi possível salvar as alterações. Tente Novamente, e se o problema persistir contate o suporte.") End Try PopularDepartamentosDropDownList(_curso.DepartamentoID) Return View(_curso) End Function ' ' GET: /Course/Edit/5 Function Edit(id As Integer) As ViewResult Dim _curso As Curso = db.Cursos.Find(id) PopularDepartamentosDropDownList(_curso.DepartamentoID) Return View(_curso) End Function ' ' POST: /Course/Edit/5 <HttpPost()> Function Edit(_curso As Curso) As ActionResult Try If ModelState.IsValid Then db.Entry(_curso).State = EntityState.Modified db.SaveChanges() Return RedirectToAction("Index") End If Catch ex As DataException 'Log the error ModelState.AddModelError("", "Não foi possível salvar as alterações. Tente Novamente, e se o problema persistir contate o suporte.") End Try PopularDepartamentosDropDownList(_curso.DepartamentoID) Return View(_curso) End Function ''' <summary> ''' Preenche um dropdownlist com departamentos ''' </summary> ''' <param name="selectedDepartment"></param> ''' <remarks></remarks> Private Sub PopularDepartamentosDropDownList(Optional departamentoSelecionado As Object = Nothing) Dim departamentoConsulta = From d In db.Departamentos Order By d.Nome Select d ViewBag.DepartamentoID = New SelectList(departamentoConsulta, "DepartamentoID", "Nome", departamentoSelecionado) End Sub ...... |
O método PopularDepartamentosDropDownList obtém uma lista de todos os departamentos ordenados por nome, cria uma coleção SelectList para uma lista dropdown, e passa a coleção para a view em uma propriedade ViewBag. O método aceita um parâmetro que permite que o chamador especifique opcionalmente o item que será selecionado inicialmente quando a lista dropdown for processado.
O método Create HttpGet chama o método PopularDepartamentosDropDownList sem definir o item selecionado, porque, para um novo curso o departamento ainda não foi definido.
O método Edit HttpGet define o item selecionado, com base na identificação do departamento que já está atribuído ao curso que está sendo editado.
Ambos métodos HttpPost tanto para criar e editar também incluem um código que define o item selecionado quando reexibir a página depois de um erro.
Vamos agora ajustar as views Create e Edit.
Abra a view Views/Curso/Create.vbhtml e adicione um novo campo antes do campo Título para permitir que o usuário digite o número do curso. Para isso inclua o código abaixo:
.... @Html.LabelFor(Function(model) model.CursoID) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.CursoID) @Html.ValidationMessageFor(Function(model) model.CursoID) </div> .... |
Agora nas views Views\Course\Edit.vbhtml, Views\Course\Delete.vbhtml e Views\Course\Details.vbhtml inclua um novo campo antes do campo Titulo para exibir o número do curso incluindo o código a seguir nestes arquivos:
....... <div class="display-label"> @Html.LabelFor(Function(model) model.CursoID) </div> <div class="display-field"> @Html.DisplayFor(Function(model) model.CursoID) </div> ...... |
Vamos executar o projeto e após exibir a página de Cursos (Index.vbhtml) clique no link Criar Novo para abrir a página para criar um novo Curso:
Ao clicar no botão Create, a página Index será exibida exibindo o novo curso adicionado na lista. O nome do departamento na página vem da propriedade de navegação mostrando que o relacionamento foi estabelecido corretamente.
Agora volte à página dos Cursos e clique no link Edit para editar um curso.
Altere o número de créditos e clique no botão Save para salvar as alterações feitas. Você deverá a página de Cursos exibindo o valor alterado.
Adicionando uma página de edição para os instrutores
Quando você edita um registro de um instrutor, você quer ser capaz de atualizar a diretoria do instrutor. A entidade instrutor tem um relacionamento um-para zero-ou-um com a entidade Diretoria, o que significa que você deve lidar com as seguintes situações:
Abra o controlador InstrutorController na pasta Controllers e altere o código do método Edit (HttpGet)
Function Edit(id As Integer) As ViewResult Dim _instrutor As Instrutor = db.Instrutores. Include(Function(i) i.Cursos). Where(Function(i) i.InstrutorID = id).Single() Return View(_instrutor) End Functiont: |
**** Código anterior substituido ***** Dim instrutor As Instrutor = db.Instrutores.Find(id) ViewBag.InstrutorID = New SelectList(db.Diretorias, "InstrutorID", "Localizacao", instrutor.InstrutorID) Return View(instrutor) **** Código anterior substituido ***** |
Este código elimina a declaração ViewBag e acrescenta o eager loading para as entidades Diretoria e Cursos associadas. (Você não precisa de cursos agora, mas você vai precisar dele mais tarde.) Não é possível realizar o eager loading com o método Find, por isso os métodos Where e Single são usados ??em vez de selecionar o instrutor.
Agora substitua o método Edit (HttPost) do controlador InstrutorController pelo código abaixo o qual trata da atualização das diretorias:
<HttpPost()> Public Function Edit(id As Integer, formCollection As FormCollection) As ActionResult Dim instrutorAtualizar = db.Instrutores.Include(Function(i) i.Diretoria).Include(Function(i) i.Cursos).Where(Function(i) i.InstrutorID = id).Single() If TryUpdateModel(instrutorAtualizar, "", Nothing, New String() {"Cursos"}) Then Try If [String].IsNullOrWhiteSpace(instrutorAtualizar.Diretoria.Localizacao) Then instrutorAtualizar.Diretoria = Nothing End If db.Entry(instrutorAtualizar).State = EntityState.Modified db.SaveChanges() Return RedirectToAction("Index") Catch generatedExceptionName As DataException ModelState.AddModelError("", "Não foi possível alterar. Tente novamente e se o problema persistir contate o suporte.") Return View() End Try End If Return View(instrutorAtualizar) End Function |
Neste código faz o seguinte:
No arquivo Edit.vbhtml da pasta Views/Instrutor , inclua o campo para editar a diretoria depois da <div> DataAdmissao conforme abaixo:
..... <div class="editor-label"> @Html.LabelFor(Function(model) model.Diretoria.Localizacao) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.Diretoria.Localizacao) @Html.ValidationMessageFor(Function(model) model.Diretoria.Localizacao) </div> .... |
Execute o projeto e abra a página para Instrutores e a seguir clique no link Edit para editar um instrutor:
Vamos alterar a Localização (Diretoria) e clicar no botão Save;
Você deverá o novo valor exibido na página Instrutores e se você abrir a tabela Diretoria irá ver o novo valor atualizado na respectiva coluna:
Se você retornar para a página de edição e limpar a informação sobre a localização o registro será removido da tabela Diretoria:
E se retornamos para a página de edição e incluirmos um novo valor para a localização o mesmo será salvo na tabela Diretoria:
Note que depois de selecionar a entidade Curso , o código carrega explicitamente a propriedade de navegação Matricula do curso.
Incluindo a atribuição de Cursos para a página de Instrutores
Como os instrutores podem ensinar qualquer número de cursos vamos melhorar a pagina dos instrutores incluindo a possibilidade de alterar os cursos atribuídos incluindo um grupo de caixa de verificação que será usada para selecionar os cursos.
A relacionamento entre as entidades Curso e Instrutor é de muitos-para-muitos, o que significa que não temos acesso direto à tabela de junção ou campos de chave estrangeira. Em vez disso, vamos adicionar e remover entidades de e para a propriedade de navegação Instrutor.Cursos.
A interface do usuário que permite alterar quais cursos um instrutor ministra será um grupo de caixas de seleção. A caixa de seleção para cada curso no banco de dados será exibida, e os cursos que o instrutor está atualmente ministrando serão selecionados. O usuário pode marcar ou desmarcar as caixas de seleção para alterar as atribuições do curso.
Para fornecer dados para a view para a lista de caixas de seleção, vamos usar uma classe de model view criando a classe CursosAtribuidos.vb na pasta ViewModels com o seguinte código:
Imports System.Collections.Generic Imports System.ComponentModel.DataAnnotations Public Class CursosAtribuidos Public Property CursoID As Integer Public Property Titulo As String Public Property Atribuido As Boolean End Class |
No controlador InstrutorController.vb da pasta Controllers, no método Edit (Httpget) vamos chamar um novo método que fornece informação para grupo de caixa de seleção usando a nova classe view-model com o seguinte código:
Public Function Edit(id As Integer) As ActionResult Dim _instrutor As Instrutor = db.Instrutores.Include(Function(i) i.Diretoria).Include(Function(i) i.Cursos).Where(Function(i) i.InstrutorID = id).Single() PopularCursosAtribuidos(_instrutor) Return View(_instrutor) End Function Private Sub PopularCursosAtribuidos(byval _instructor As Instrutor) Dim todosCursos = db.Cursos Dim instrutorCursos = New HashSet(Of Integer)(_instructor.Cursos.[Select](Function(c) c.CursoID)) Dim viewModel = New List(Of CursosAtribuidos)() For Each _curso In todosCursos viewModel.Add(New CursosAtribuidos() With { _ .CursoID = _curso.CursoID, _ .Titulo = _curso.Titulo, _ .Atribuido = instrutorCursos.Contains(_curso.CursoID) _ }) Next ViewBag.Cursos = viewModel End Sub |
O código no novo método lê todas as entidades do curso, a fim de carregar uma lista de cursos que utilizam a classe view-model. Para cada curso, o código verifica se o curso existe na propriedade de navegação Cursos do instrutor. Para criar uma pesquisa eficiente quando da verificação se um curso esta atribuido ao instrutor, os cursos designados para o instrutor são colocados em uma coleção HashSet. A propriedade Atribuido dos cursos que são atribuídas ao instrutor é definido como True(verdadeiro). A exibição vai usar essa propriedade para determinar quais as caixas de seleção devem ser exibidas como selecionadas. Finalmente, a lista é passado para a view na propriedade ViewBag.
A seguir vamos adicionar o código o que será executado quando o usuário clicar em Salvar. Substitua o método Edit HttpPost com o código abaixo, que chama de um novo método que atualiza a propriedade de navegação Cursos da entidade Instrutor.
<HttpPost> _ Public Function Edit(id As Integer, formCollection As FormCollection, cursosSelecionados As String()) As ActionResult Dim _instrutorAtualizar = db.Instrutores.Include(Function(i) i.Diretoria).Include(Function(i) i.Cursos).Where(Function(i) i.InstrutorID = id).Single() If TryUpdateModel(_instrutorAtualizar, "", Nothing, New String() {"Cursos"}) Then Try If [String].IsNullOrWhiteSpace(_instrutorAtualizar.Diretoria.Localizacao) Then _instrutorAtualizar.Diretoria = Nothing End If AtualizaCursosInstrutores(cursosSelecionados, _instrutorAtualizar) db.Entry(_instrutorAtualizar).State = EntityState.Modified db.SaveChanges() Return RedirectToAction("Index") Catch generatedExceptionName As DataException 'Log the error ModelState.AddModelError("", "Não foi possível salvar as alterações. Tente novamente e se o problema persistir contate o suporte.") End Try End If PopularCursosAtribuidos(_instrutorAtualizar) Return View(_instrutorAtualizar) End Function |
Private Sub AtualizaCursosInstrutores(_cursosSelecionados As String(), _instrutorAtualizar As Instrutor) If _cursosSelecionados Is Nothing Then _instrutorAtualizar.Cursos = New List(Of Curso)() Return End If Dim _cursoSelecionaedosHS = New HashSet(Of String)(_cursosSelecionados) Dim instructorCourses = New HashSet(Of Integer)(_instrutorAtualizar.Cursos.Select(Function(c) c.CursoID)) For Each course In db.Cursos If _cursoSelecionaedosHS.Contains(course.CursoID.ToString()) Then If Not instructorCourses.Contains(course.CursoID) Then _instrutorAtualizar.Cursos.Add(course) End If Else If instructorCourses.Contains(course.CursoID) Then _instrutorAtualizar.Cursos.Remove(course) End If End If Next End Sub |
vamos entender o código:
Na view /Views/Instrutor/Edit.vbhtml vamos incluir o campo Cursos com um array de caixas de seleção usando o código a seguir depois da div elementos do campo Diretoria:
<div class="editor-field"> <table style="width: 100%"> <tr> @Code Dim cnt as Integer = 0 Dim _cursos As List(Of UniversidadeMacoratti.CursosAtribuidos) = ViewBag.Cursos For Each _curso As UniversidadeMacoratti.CursosAtribuidos In _cursos If cnt Mod 3 = 0 Then @:</tr> <tr> End If cnt += 1 @<td> <input type="checkbox" name="_cursosSelecionados" value=@Html.Raw(("""" & _curso.CursoID & """")) @IIf(_curso.Atribuido, "checked =""checked""", "") /> @_curso.CursoID @_curso.Titulo </td> Next @:</tr> End Code </table> </div> |
Este código cria uma tabela HTML que tem três colunas. Em cada coluna temos uma caixa de seleção seguida de uma legenda que consiste no número curso e seu título. Todas caixas de seleção têm o mesmo nome ("_cursosSelecinados"), que informa ao model binder que eles devem ser tratados como um grupo. O atributo de valor cada caixa de seleção está definido para o valor de CursoID. Quando a página é postada, o model binder passa uma matriz para o controlador, que consiste dos valores CursoID somente para as caixas de seleção que são selecionadas.
Quando as caixas são inicialmente renderizadas, aquelas que são para os cursos já atribuídos ao professor possui o atributo checked sendo assim marcada.
Depois de alterar as atribuições do curso, queremos poder verificar as alterações quando o site retorna para a página Index. Portanto, precisamos adicionar uma coluna para os cursos para a tabela na página Index.vbhtml conforme abaixo:
<tr> <th>SobreNome</th> <th>Nome</th> <th>DataAdmissao</th> <th>Diretoria</th> <th>Cursos</th> <th></th> </tr> |
E ainda temos que incluir um nova célula para exibir os cursos logo após a célula que exibe a Diretoria. Para isso vamos incluir o código abaixo na view Index.vbhtml do Instrutor:
<td> @Code For Each _curso As UniversidadeMacoratti.UniversidadeMacoratti.Models.Curso In item.Cursos @_curso.CursoID @: @_curso.Titulo <br /> Next End Code </td> |
Executando o projeto após essas alterações a acionando a página de instrutores teremos o seguinte resultado:
Observe que agora os cursos de cada instrutor são exibidos juntamente com o código de cada curso.
Clicando no link Edit para um instrutor iremos obter:
Vemos a exibição dos cursos sendo que os cursos atribuídos ao instrutor possuem as caixas de seleção marcadas.
Pegue o projeto completo aqui: UniversidadeMacoratti_6.zip (abra no Visual Studio 2012 Express for web)
1Co 2:14
Ora, o homem natural não aceita as coisas do Espírito de Deus, porque para ele são loucura; e não pode entendê-las, porque elas se discernem espiritualmente.1Co 2:15
Mas o que é espiritual discerne bem tudo, enquanto ele por ninguém é discernido.Referências: