Neste artigo eu vamos iniciar a criação de uma aplicação ASP .NET Core MVC usando o Entity Framework Core no Visual Studio. |
Continuando a terceira parte do artigo vamos criar e ajustar os métodos CRUD - Create , Read, Update e Delete gerados pelo Scaffolding.
Customizando os métodos CRUD
Até o momento temos uma aplicação Web MVC que permite acessar e exibir informações de um banco de dados SQL Server Local DB usando o Entity Framework.
No artigo anterior nós criamos um controlador e as respectivas views para gerenciar as informações sobre estudantes usando o Scaffolding, e, dessa forma agora podemos acessar e exibir os dados dos estudantes, bem como, editar, incluir e excluir suas informações.
Vamos rever e customizar o código CRUD gerado pelo Scaffolding iniciando pela página de detalhes de um estudante: a página Details
Executando a aplicação e clicando no link Detalhes iremos veremos a exibição da página Details abaixo:
Observe que o código gerado pelo Scaffolding para página não esta exibindo as informações sobre a propriedade Matriculas definida na classe Estudante, porque esta propriedade representa uma coleção. A página portanto não esta exibindo as matriculas feitas pelo estudante.
Vamos ajustar isso e exibir a coleção de matriculas em uma tabela HTML. Para começar temos que primeiro alterar o código do método Action Details do controlador EstudantesController.
No código original gerado pelo Scaffolding o método Action Details utiliza o método SingleOrDefaulAsync para recuperar uma única entidade Estudante.
Vamos alterar o código original (que foi comentado) incluindo o código destacado em azul conforme mostrado abaixo:
// GET: Estudantes/Details/5 public async Task<IActionResult> Details(int? id) { if (id == null) { return NotFound(); }
//var estudante = await _context.Estudantes var estudante = await _context.Estudantes if (estudante == null) |
Alteramos a consulta para agora retornar um estudante e suas matriculas onde os métodos Include e ThenInclude fazem com que o contexto carregue a propriedade de navegação Estudantes.Matriculas e dentro de cada inscrição a propriedade de navegação Matricula.Curso.
O método AsNoTracking melhora o desempenho em cenários onde as entidades retornadas não serão atualizadas no tempo de vida do contexto atual.
Nota: O método de extensão AsNoTracking() retorna uma nova consulta e as entidades retornadas não serão armazenadas em cache pelo contexto (DbContext ou Object Context). Isso significa que o Entity Framework não executa qualquer processamento ou armazenamento adicional das entidades retornadas pela consulta. Observe que não é possível atualizar essas entidades sem anexar ao contexto.
O valor chave (Id) que é passado para o método Details vem dos dados da rota. Os dados de rota são dados que o model binder encontra em um segmento da URL. Por exemplo, a rota padrão especifica os segmentos controller, action e id conforme vemos abaixo:
App.UseMvc (routes => { Routes.MapRoute ( Nome: "default", Template: "{controller = Home} / {action = Index} / {id?}"); }); |
Nota : O model binder da ASP.NET Core MVC mapeia dados de solicitações HTTP para parâmetros dos métodos Actions. Os parâmetros podem ser tipos simples, como strings, inteiros ou floats, ou podem ser tipos complexos. |
Na URL a seguir, a rota padrão mapeia o Instrutor como
sendo o Controller, Index como a
sendo a Action e 1 como o id; Estes são valores
de dados de rota.
Http://localhost:1230/Instrutor/Index/1?CursoID=2021
A última parte do URL ("?CursoID=2021") é um
valor de seqüência de caracteres de consulta. O model
binder também passará o valor ID para o parâmetro ID
do método Details se o passar como um valor de
cadeia de consulta:
Http://localhost:1230/Instrutor/Index?Id=1&CursoID=2021
Na página Index, as URLs de hiperlink são criadas por
instruções de tag helper na exibição Razor. No código
Razor a seguir, o parâmetro id corresponde à rota
padrão, assim id é adicionado aos dados da rota.
<a asp-action="Edit"
asp-route-id="@item.ID">Editar</a>
Quando o valor de ID for 6 esse código gera o seguinte
HTML:
<a href="/Estudantes/Edit/6">Editar</a>
Agora temos que exibir as informações na respectiva View.
Para isso vamos abrir a view Details.cshtml na pasta Views/Estudantes e vamos incluir o código em azul conforme abaixo:
@model UniversidadeMacoratti.Models.Estudante @{ ViewData["Title"] = "Details"; } <h2>Detalhes</h2> <div> |
O código inserido cria uma tabela HTML e itera através das entidades na propriedade de navegação Matriculas onde para cada matricula ele exibe o nome do curso e a nota.
O nome do curso é retornado a partir da entidade Curso que esta armazenada na propreidade de navegação da entidade Matriculas.
Executando o projeto novamente e clicando no link detalhes para um estudante agora iremos obter o seguinte resultado:
Atualizando a página Create
Vamos agora ajustar o código do método Create do controlador EstudantesController conforme o código a seguir:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("SobreNome,Nome,DataMatricula")] Estudante estudante) { try { if (ModelState.IsValid) { _context.Add(estudante); await _context.SaveChangesAsync(); return RedirectToAction("Index"); } } catch (DbUpdateException /* ex */) { //Logar o erro (descomente a variável ex e escreva um log ModelState.AddModelError("", "Não foi possível salvar. " + "Tente novamente, e se o problema persistir " + "chame o suporte."); } return View(estudante); } |
Este código adiciona a
entidade Estudante criada pelo
model binder da ASP.NET MVC ao conjunto de
entidades Estudantes e salva as alterações no
banco de dados.
Nós removemos EstudanteID do atributo Bind
porque EstudanteID é o valor da chave primária
que o SQL Server definirá automaticamente quando a linha
for inserida. A entrada do usuário não define o valor
ID.
Diferente do atributo Bind, o bloco try-catch
é a única alteração que fizemos no código gerado. Se uma
exceção que deriva de DbUpdateException é
detectada enquanto as alterações estão sendo salvas, uma
mensagem de erro genérica é exibida. As exceções do
DbUpdateException às vezes são causadas por algo
externo ao aplicativo, em vez de um erro de programação,
pelo que o usuário é aconselhado a tentar novamente.
Embora não implementado neste exemplo, um aplicativo de
qualidade de produção registraria a
exceção usando um log.
Nota: O
atributo Bind do código gerado inclui no método
Create é uma maneira de se proteger contra o
overposting em cenários de criação de informações. Ele
limita que o model binder usa quando ele cria uma
instância de Estudante.
O atributo ValidateAntiForgeryToken ajuda a
evitar ataques de falsificação de solicitação entre
sites (CSRF). O token é automaticamente injetado na
visualização por FormTagHelper e é incluído
quando o formulário é enviado pelo usuário. O token é
validado pelo atributo ValidateAntiForgeryToken.
Vamos testar a criação de um novo Estudante. Execute a aplicação novamente e clique no link Estudantes e a seguir em Criar Novo;
Informe os dados para o novo estudante e clique no botão Criar;
Vemos o resultado exibido nas páginas abaixo:
Ajustando a página para Editar dados: O método Edit (HttpGet e HttpPost)
Vamos agora alterar o código dos métodos Edit do controlador EstudantesController. Note que temos dois métodos Edit. Um que não usa nenhum atributo, e , neste caso é o método Edit HttpGet e o outro método que usa o atributo [HttPost].
O ´método HttpGet não precisa ser alterado. Vamos alterar o método Edit HttpPost conforme mostrado abaixo:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var atualizarEstudante = await _context.Estudantes.SingleOrDefaultAsync(s => s.EstudanteID == id);
if (await TryUpdateModelAsync<Estudante>(
atualizarEstudante,
"",
s => s.Nome, s => s.SobreNome, s => s.DataMatricula))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//Logar o erro (descomente a variável ex e escreva um log
ModelState.AddModelError("", "Não foi possível salvar. " +
"Tente novamente, e se o problema persistir " +
"chame o suporte.");
}
}
return View(atualizarEstudante);
}
|
As mudanças feitas no
código implementam uma prática recomendada de segurança
para evitar a sobreposição de post. O código gerado pelo
Scaffolding usava o atributo Bind e adicionava a
entidade criada pelo model binder ao conjunto de
entidades com um sinalizador Modified. Esse
código não é recomendado para muitos cenários porque o
atributo Bind limpa quaisquer dados
pré-existentes em campos não listados no parâmetro
Include.
O novo código lê a entidade existente e chama
TryUpdateModel para atualizar os campos na entidade
recuperada com base na entrada do usuário nos dados do
formulário postados.
O controle de alterações automático do Entity Framework
define o sinalizador Modified nos campos que são
alterados pela entrada de formulário. Quando o método
SaveChanges é chamado, o Entity Framework cria
instruções SQL para atualizar o registro do banco de
dados. Os conflitos de simultaneidade são ignorados e
somente as colunas da tabela atualizadas pelo usuário
são atualizadas no banco de dados.
Como uma prática mais recomendada para evitar o
overposting, os campos que você deseja que sejam
atualizáveis pela página Editar estão na lista
vazia nos parâmetros TryUpdateModel. (A string
vazia que precede a lista de campos na lista de
parâmetros é para um prefixo a ser usado com os nomes
dos campos de formulário.) Atualmente, não há campos
extras que você está protegendo, mas listando os campos
que você deseja que o model binder vincule
garante que, se você adicionar campos ao modelo de dados
no futuro, eles serão protegidos automaticamente até que
você os adicione explicitamente aqui.
Como resultado dessas alterações, a assinatura de método
do método HttpPost Edit é a mesma que o método
HttpGet Edit; por isso alteramos o nome do método
para EditPost.
O contexto do banco
de dados controla se as entidades na memória estão
sincronizadas com suas linhas correspondentes no banco
de dados e essas informações determinam o que acontece
quando você chama o método SaveChanges. Por
exemplo, quando você passa uma nova entidade para o
método Add, o estado dessa entidade é definido
como Added e a seguir, quando você chama o método
SaveChanges, o contexto do banco de dados emite um
comando SQL INSERT.
O estado da entidade é uma enumeração do tipo
System.Data.EntityState que declara os seguintes
valores:
Added - A entidade é marcada como adicionada;
Deleted - A entidade é marcada como deletada;
Modified - A entidade foi modificada;
Unchanged - A entidade não foi modificada;
Detached - A entidade não esta sendo tratada no
contexto;
Em um aplicativo desktop, as alterações de estado são
normalmente definidas automaticamente. Você lê uma
entidade e faz alterações em alguns de seus valores de
propriedade. Isso faz com que seu estado seja alterado
automaticamente para Modified. Em seguida, quando
você chamar SaveChanges, o Entity Framework gera
uma instrução SQL UPDATE que atualiza somente as
propriedades reais que você alterou.
Em um aplicação web, o DbContext que inicialmente
lê uma entidade e exibe seus dados a serem editados é
descartado após uma página ser processada. Quando o
método Action HttpPost Edit é chamado, uma nova
solicitação é feita e você tem uma nova instância do
DbContext. Se você reler a entidade nesse novo
contexto, você simula o processamento da área de
trabalho.
Agora vamos testar a edição de dados.
Execute a aplicação e clique no link Editar para um estudante e altere uma informação clicando a seguir no botão Salvar.
Abaixo vemos o resultado da alteração feita.
Ajustando o método Delete e sua View
Como vimos nas operações para atualizar e criar um estudante, a operação para excluir um estudante também exige dois métodos Action.
Vamos adicionar
um bloco try-catch ao método HttpPost
Delete para lidar com quaisquer erros que
possam ocorrer quando o banco de dados for
atualizado. Se ocorrer um erro, o método
HttpPost Delete chama o método HttpGet
Delete, passando um parâmetro que indica que
ocorreu um erro. O método HttpGet Delete
então exibe novamente a página de confirmação
juntamente com a mensagem de erro, dando ao
usuário a oportunidade de cancelar ou tentar
novamente.
Substitua o método Action HttpGet Delete
com o código a seguir, que gerencia o relatório
de erros.
public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
var estudante = await _context.Estudantes
.AsNoTracking()
.SingleOrDefaultAsync(m => m.EstudanteID == id);
if (estudante == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"A exclusão falhou. Tente novamente e se o problema persistir " +
"contate o suporte.";
}
return View(estudante);
}
|
Este código aceita um parâmetro opcional que indica se o método foi chamado após uma falha para salvar as alterações. Este parâmetro é false quando o método HttpGet Delete é chamado sem uma falha anterior. Quando ele é chamado pelo método HttpPost Delete em resposta a um erro de atualização de banco de dados, o parâmetro é true e uma mensagem de erro é passada para a exibição
Agora vamos ajustar o método HttPost Delete substitindo o método Action HttpPost Delete pelo código abaixo onde alteramos o nome do método para DeleteConfirmed.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var estudante = await _context.Estudantes
.AsNoTracking()
.SingleOrDefaultAsync(m => m.EstudanteID == id);
if (estudante == null)
{
return RedirectToAction("Index");
}
try
{
_context.Estudantes.Remove(estudante);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//Logar o erro
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
}
|
Este código retorna uma entidade selecionada, e então chama o método Remove para definir o status da entidade como Deleted. Quando o método SaveChanges for invocado, o comando SQL DELETE e gerado para excluir o registro da tabela.
Agora para concluir vamos alterar a view Delete.cshtml na pasta /Views/Estudantes incluindo uma mensagem de erro na página conforme o código abaixo:
@model UniversidadeMacoratti.Models.Estudante
@{
ViewData["Title"] = "Delete";
}
<h2>Deletar</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Tem certeza que deseja deletar este registro?</h3>
<div>
<h4>Estudante</h4>
<hr />
<dl class="dl-horizontal">
<input type="hidden" asp-for="EstudanteID" />
<dt>
@Html.DisplayNameFor(model => model.SobreNome)
</dt>
<dd>
@Html.DisplayFor(model => model.SobreNome)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Nome)
</dt>
<dd>
@Html.DisplayFor(model => model.Nome)
</dd>
<dt>
@Html.DisplayNameFor(model => model.DataMatricula)
</dt>
<dd>
@Html.DisplayFor(model => model.DataMatricula)
</dd>
</dl>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Deletar" class="btn btn-default" /> |
<a asp-action="Index">Retornar para Lista</a>
</div>
</form>
</div>
|
Agora vamos testar a exclusão de dados executando novamente a aplicação, selecionado um estudante e clicando no botão Deletar:
A página Index irá exibir a relação de estudantes sem o estudante excluído.
E assim concluímos os ajustes nas operações CRUD para a nossa entidade Estudante.
Fechando
conexões de banco de dados
Para liberar os recursos que uma conexão de banco de
dados utiliza, a instância de contexto deve ser
descartada assim que você terminar de usá-la. A injeção
de dependência incorporada do ASP.NET Core cuida dessa
tarefa para você.
No arquivo Startup.cs chamamos o método de
extensão AddDbContext para provisionar a classe
DbContext no recipiente
ASP.NET DI. Esse método define a vida útil do
serviço como Scoped por padrão. Scoped significa
que o tempo de vida do objeto de contexto coincide com o
tempo de vida da requisição web e o método Dispose será
chamado automaticamente no final da solicitação web.
Gerenciando Transações
Por padrão, o Entity Framework implícitamente implementa
transações. Nos cenários em que você faz alterações em
várias linhas ou tabelas e, em seguida, chama
SaveChanges, o Entity Framework automaticamente garante
que todas as alterações sejam bem-sucedidas ou falhem.
Se algumas alterações são feitas primeiro e, em seguida,
ocorre um erro, essas alterações são automaticamente
revertidas.
Consultas de não acompanhamento (No-Tracking)
Quando um contexto de banco de dados recupera linhas de
uma tabela e cria objetos de entidade que os
representam, por padrão ele mantém um registro se as
entidades na memória estão sincronizadas com o que está
no banco de dados. Os dados na memória atuam como um
cache e são usados quando você atualiza uma entidade.
Esse armazenamento em cache é muitas vezes desnecessário
em um aplicativo Web porque as instâncias de contexto
são normalmente de curta duração (uma nova é criada e
disposta para cada solicitação) e o contexto que lê
uma entidade normalmente é descartado antes que essa
entidade seja usada novamente.
Você pode desabilitar o rastreamento de objetos de
entidade na memória chamando o método AsNoTracking.
Isso melhora o desempenho.
A seguir algumas
situações onde podemos desabilitar o rastreamento :
1 - Durante a vida do contexto, você não precisa
atualizar quaisquer entidades e não precisa que o EF
carregue automaticamente propriedades de navegação com
entidades recuperadas por consultas separadas.
Frequentemente, estas condições são satisfeitas nos
métodos Action HttpGet do
controlador.
2 - Você está executando uma consulta que recupera um
grande volume de dados e somente uma pequena parte dos
dados retornados serão atualizados. Pode ser mais
eficiente desativar o rastreamento para a consulta e
executar uma consulta mais tarde para as poucas
entidades que precisam ser atualizadas.
3 - Você deseja anexar uma entidade para atualizá-la,
mas antes você recuperou a mesma entidade para uma
finalidade diferente. Como a entidade já está sendo
rastreada pelo contexto do banco de dados, não é
possível anexar a entidade que você deseja alterar. Uma
maneira de lidar com essa situação é chamar AsNoTracking
na consulta anterior.
Na próxima parte do artigo vamos continuar realizando operações de filtragem, ordenação, paginação e agrupamento.
Eu sou a videira, vós as varas; quem está em mim, e eu
nele, esse dá muito fruto; porque sem mim nada podeis
fazer.
João 15:5
Veja os
Destaques e novidades do SUPER DVD Visual Basic
(sempre atualizado) : clique e confira !
Quer migrar para o VB .NET ?
Quer aprender C# ??
Quer aprender os conceitos da Programação Orientada a objetos ? Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ? |
Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
Entity Framework - Conceitos Básicos - Uma visão geral - Macoratti
Entity Framework - Separando as classes das entidades do ... - Macoratti
Entity Framework 6 - Aplicação em camadas - Definindo o ... - Macoratti
C# - Cadastro de Clientes com Entity Framework em ... - Macoratti
NET - Entity Framework 5 - Operações CRUD (revisitado) - Macoratti