Tutorial: Saiba mais sobre cenários avançados – ASP.NET MVC com EF Core
No tutorial anterior, você implementou a herança de tabela por hierarquia. Este tutorial apresenta vários tópicos que são úteis para se estar ciente quando você vai além dos conceitos básicos no desenvolvimento de aplicativos web ASP.NET Core que usam o Entity Framework Core.
Neste tutorial, você:
- Executar consultas SQL brutas
- Chamar uma consulta para retornar entidades
- Chamar uma consulta para outros tipos
- Executar uma consulta de atualização
- Examinar consultas SQL
- Criar uma camada de abstração
- Saiba mais sobre a detecção automática de alterações
- Saiba mais sobre EF Core código-fonte e planos de desenvolvimento
- Saiba como usar o LINQ dinâmico para simplificar o código
Pré-requisitos
Executar consultas SQL brutas
Uma das vantagens de usar o Entity Framework é que ele evita a vinculação do código muito próximo a um método específico de armazenamento de dados. Ele faz isso gerando consultas SQL e comandos para você, liberando-o de ter que escrevê-los por conta própria. Mas há cenários excepcionais quando você precisa executar consultas SQL específicas que você criou manualmente. Para esses cenários, a API Entity Framework Code First inclui métodos que permitem passar comandos SQL diretamente para o banco de dados. Você tem as seguintes opções no EF Core 1.0:
Use o método
DbSet.FromSql
para consultas que retornam tipos de entidade. Os objetos retornados devem ser do tipo esperado pelo objetoDbSet
e são rastreados automaticamente pelo contexto do banco de dados, a menos que você desative o rastreamento.Use o
Database.ExecuteSqlCommand
para comandos que não são de consulta.
Se você precisar executar uma consulta que retorne tipos que não são entidades, poderá usar ADO.NET com a conexão de banco de dados fornecida pelo EF. Os dados retornados não são acompanhados pelo contexto do banco de dados, mesmo se você usar esse método para recuperar tipos de entidade.
Como sempre é verdade quando você executa comandos SQL em um aplicativo Web, você deve tomar precauções para proteger seu site contra ataques de injeção de SQL. Uma maneira de fazer isso é usar consultas parametrizadas para garantir que as cadeias de caracteres enviadas por uma página da Web não possam ser interpretadas como comandos SQL. Neste tutorial, você usará consultas parametrizadas ao integrar a entrada do usuário em uma consulta.
Chamar uma consulta para retornar entidades
A classe DbSet<TEntity>
fornece um método que você pode usar para executar uma consulta que retorna uma entidade do tipo TEntity
. Para ver como isso funciona, você alterará o código no método Details
do controlador de departamento.
Em DepartmentsController.cs
, no método Details
, substitua o código que recupera um departamento por uma chamada de método FromSql
, conforme mostrado no seguinte código realçado:
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync();
if (department == null)
{
return NotFound();
}
return View(department);
}
Para verificar se o novo código funciona corretamente, selecione a guia Departamentos e, em seguida, Detalhes de um dos departamentos.
Detalhes do Departamento
Chamar uma consulta para outros tipos
Anteriormente, você criou uma grade de estatísticas de alunos para a página Sobre que mostrava o número de alunos para cada data de registro. Você obteve os dados do conjunto de entidades Students (_context.Students
) e usou LINQ para projetar os resultados em uma lista de objetos de modelo de exibição EnrollmentDateGroup
. Suponha que você queira escrever o SQL em si em vez de usar LINQ. Para fazer isso, você precisa executar uma consulta SQL que retorna algo diferente de objetos de entidade. No EF Core 1.0, uma maneira de fazer isso é escrever código ADO.NET e obter a conexão de banco de dados do EF.
Em HomeController.cs
, substitua o método About
pelo seguinte código:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
Adicionar uma instrução using:
using System.Data.Common;
Execute o aplicativo e vá para a página Sobre. Ele exibe os mesmos dados que antes.
Chamar uma consulta de atualização
Suponha que os administradores da Contoso University queiram realizar alterações globais no banco de dados, como alterar o número de créditos para cada curso. Se a universidade tiver um grande número de cursos, seria ineficiente recuperá-los todos como entidades e alterá-los individualmente. Nesta seção, você implementará uma página da Web que permite que o usuário especifique um fator para alterar o número de créditos para todos os cursos e fará a alteração executando uma instrução SQL UPDATE. A página da Web terá a seguinte aparência:
Atualize a página de Créditos do Curso
Em CoursesController.cs
, adicione métodos UpdateCourseCredits para HttpGet e HttpPost:
public IActionResult UpdateCourseCredits()
{
return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}
Quando o controlador processa uma solicitação HttpGet, nada é retornado em ViewData["RowsAffected"]
e o modo de exibição exibe uma caixa de texto vazia e um botão enviar, conforme mostrado na ilustração anterior.
Quando o botão Atualizar recebe um clique, o método HttpPost é chamado e multiplicador tem o valor inserido na caixa de texto. Em seguida, o código executa o SQL que atualiza os cursos e retorna o número de linhas afetadas para a exibição em ViewData
. Quando a visualização obtém um valor RowsAffected
, ela exibe o número de linhas atualizadas.
No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Views/Courses e, em seguida, clique em Adicionar> Novo Item.
Na caixa de diálogo Adicionar Novo Item, clique em ASP.NET Core em Instalado no painel esquerdo, clique em Exibição do Razor, e nomeie a nova exibição como UpdateCourseCredits.cshtml
.
Em Views/Courses/UpdateCourseCredits.cshtml
, substitua o código do modelo pelo seguinte código:
@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>
@if (ViewData["RowsAffected"] == null)
{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default" />
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Execute o método UpdateCourseCredits
selecionando a guia Cursos e adicionando "/UpdateCourseCredits" ao final da URL na barra de endereços do navegador (por exemplo: http://localhost:5813/Courses/UpdateCourseCredits
). Insira um número na caixa de texto:
página
Clique em Atualizar. Você verá o número de linhas afetadas:
Atualizar linhas afetadas na página de Créditos do Curso
Clique em Voltar à Lista para ver a lista de cursos com o número revisado de créditos.
Observe que o código de produção garantirá que as atualizações sempre resultem em dados válidos. O código simplificado mostrado aqui pode multiplicar o número de créditos o suficiente para resultar em números maiores que 5. (A propriedade Credits
tem um atributo [Range(0, 5)]
.) A consulta de atualização funcionaria, mas os dados inválidos podem causar resultados inesperados em outras partes do sistema que pressupõem que o número de créditos seja 5 ou menos.
Para obter mais informações sobre consultas SQL brutas, consulte a seção consultas SQL brutas.
Examinar consultas SQL
Às vezes, é útil poder ver as consultas SQL reais que são enviadas para o banco de dados. A funcionalidade de log interno do ASP.NET Core é usada automaticamente por EF Core para gravar logs que contêm o SQL para consultas e atualizações. Nesta seção, você verá alguns exemplos de log do SQL.
Abra StudentsController.cs
e, no método Details
, defina um ponto de interrupção na instrução if (student == null)
.
Execute o aplicativo no modo de depuração e acesse a página Detalhes de um aluno.
Acesse a janela de Saída mostrando a saída de depuração e você verá a consulta:
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]
Você observará algo aqui que pode surpreendê-lo: o SQL seleciona no máximo duas linhas (TOP(2)
) da tabela Pessoa. O método SingleOrDefaultAsync
não se resolve para uma única linha no servidor. Veja o motivo:
- Se a consulta retornar várias linhas, o método retornará nulo.
- Para determinar se a consulta retornaria várias linhas, o EF precisa verificar se ela retorna pelo menos 2.
Observe que você não precisa usar o modo de depuração e parar em um ponto de interrupção para obter a saída de log na janela de Saída. É apenas um modo conveniente de parar o log no ponto em que você deseja examinar a saída. Se você não fizer isso, o log continuará e você precisará rolar para baixo para localizar as partes de seu interesse.
Criar uma camada de abstração
Muitos desenvolvedores escrevem código para implementar o repositório e a unidade de padrões de trabalho como um wrapper em torno do código que funciona com o Entity Framework. Esses padrões destinam-se a criar uma camada de abstração entre a camada de acesso a dados e a camada lógica de negócios de um aplicativo. A implementação desses padrões pode ajudar a isolar seu aplicativo de alterações no armazenamento de dados e pode facilitar o TDD (teste de unidade) automatizado ou o TDD (desenvolvimento controlado por teste). No entanto, escrever código adicional para implementar esses padrões nem sempre é a melhor opção para aplicativos que usam o EF, por vários motivos:
A própria classe de contexto EF isola seu código do código específico do armazenamento de dados.
A classe de contexto EF pode atuar como uma classe de unidade de trabalho para atualizações de banco de dados que você faz usando o EF.
O EF inclui recursos para implementar o TDD sem escrever código de repositório.
Para obter informações sobre como implementar o repositório e a unidade de padrões de trabalho, consulte a versão Entity Framework 5 desta série de tutoriais.
O Entity Framework Core implementa um provedor de banco de dados na memória que pode ser usado para teste. Para obter mais informações, confira Testar com InMemory.
Detecção automática de alterações
O Entity Framework determina como uma entidade foi alterada (e, portanto, quais atualizações precisam ser enviadas ao banco de dados) comparando os valores atuais de uma entidade com os valores originais. Os valores originais são armazenados quando a entidade é consultada ou anexada. Alguns dos métodos que causam a detecção automática de alterações são os seguintes:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Entries
Se você estiver acompanhando um grande número de entidades e chamar um desses métodos muitas vezes em um loop, poderá obter melhorias significativas de desempenho desativando temporariamente a detecção automática de alterações usando a propriedade ChangeTracker.AutoDetectChangesEnabled
. Por exemplo:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
EF Core código-fonte e planos de desenvolvimento
O código-fonte do Entity Framework Core está em https://github.com/dotnet/efcore. O repositório do EF Core contém builds noturnos, acompanhamento de questões, especificações de recurso, notas de reuniões de design e o roteiro para desenvolvimento futuro. Você pode reportar ou encontrar bugs e contribuir.
Embora o código-fonte esteja aberto, o Entity Framework Core tem suporte total como um produto da Microsoft. A equipe do Microsoft Entity Framework mantém o controle sobre quais contribuições são aceitas e testa todas as alterações de código para garantir a qualidade de cada versão.
Fazer engenharia reversa do banco de dados existente
Para fazer engenharia reversa de um modelo de dados, incluindo classes de entidade de um banco de dados existente, use o comando scaffold-dbcontext. Consulte o tutorial de introdução.
Usar LINQ dinâmico para simplificar o código
O terceiro tutorial desta série mostra como escrever um código LINQ embutindo nomes de colunas em código em uma instrução switch
. Ao escolher entre duas colunas, isso funciona bem, mas se você tiver muitas colunas, o código poderá se tornar verboso. Para resolver esse problema, você pode usar o método EF.Property
para especificar o nome da propriedade como uma cadeia de caracteres. Para experimentar essa abordagem, substitua o método Index
no StudentsController
pelo código a seguir.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
pageNumber ?? 1, pageSize));
}
Confirmações
Tom Dykstra e Rick Anderson (twitter @RickAndMSFT) escreveram este tutorial. Rowan Miller, Diego Vega e outros membros da equipe do Entity Framework ajudaram com revisões de código e ajudaram a depurar problemas que surgiram enquanto estávamos escrevendo código para os tutoriais. John Parente e Paul Goldman trabalharam na atualização do tutorial para o ASP.NET Core 2.2.
Solucionar problemas de erros comuns
ContosoUniversity.dll usada por outro processo
Mensagem de erro:
Não é possível abrir '... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' porque está sendo usado por outro processo.
Solução:
Pare o site no IIS Express. Acesse a Bandeja do Sistema do Windows, localize o IIS Express e clique com o botão direito do mouse em seu ícone, selecione o site da Contoso University e, em seguida, clique em Parar Site.
Migração gerada por scaffolding sem nenhum código nos métodos Up e Down
Causa possível:
Os comandos da CLI do EF não fecham e salvam arquivos de código automaticamente. Se você tiver alterações não salvas ao executar o comando migrations add
, o EF não encontrará suas alterações.
Solução:
Execute o comando migrations remove
, salve as alterações de código e execute novamente o comando migrations add
.
Erros durante a execução da atualização do banco de dados
É possível obter outros erros ao fazer alterações de esquema em um banco de dados que tenha dados existentes. Se você receber erros de migração que não puder resolver, poderá alterar o nome do banco de dados na cadeia de conexão ou excluir o banco de dados. Com um novo banco de dados, não há dados para migrar e o comando update-database é muito mais provável de ser concluído sem erros.
A abordagem mais simples é renomear o banco de dados em appsettings.json
. Na próxima vez que você executar database update
, um novo banco de dados será criado.
Para excluir um banco de dados no SSOX, clique com o botão direito do mouse no banco de dados, clique em Excluire, na caixa de diálogo Excluir Banco de Dados, selecione Fechar conexões existentes e clique em OK.
Para excluir um banco de dados usando a CLI, execute o comando database drop
CLI:
dotnet ef database drop
Erro ao localizar a instância do SQL Server
Mensagem de erro:
Ocorreu um erro relacionado à rede ou específico da instância ao estabelecer uma conexão com o SQL Server. O servidor não foi encontrado ou não estava acessível. Verifique se o nome da instância está correto e se o SQL Server está configurado para permitir conexões remotas. (provedor: Interfaces de Rede SQL, erro: 26 – Erro localizando servidor/instância especificado)
Solução:
Verifique a string de conexão. Se você tiver excluído manualmente o arquivo de banco de dados, altere o nome do banco de dados na cadeia de caracteres de construção para começar com um novo banco de dados.
Obter o código
Baixar ou exibir o aplicativo concluído.
Recursos adicionais
Para obter mais informações sobre EF Core, consulte a documentação do Entity Framework Core . Um manual também está disponível: Entity Framework Core in Action (Entity Framework Core em ação).
Para saber mais sobre como implantar um aplicativo Web, confira Hospedar e implantar ASP.NET Core.
Para obter informações sobre outros tópicos relacionados ao ASP.NET Core MVC, como autenticação e autorização, consulte Visão geral do ASP.NET Core.
Para obter diretrizes sobre como criar um aplicativo ASP.NET Core confiável, seguro, com desempenho, testável e escalonável, consulte padrões de aplicativo Web Enterprise. Está disponível um aplicativo web de exemplo completo com qualidade de produção que implementa os padrões.
Próximas etapas
Neste tutorial, você:
- Consultas SQL brutas executadas
- Chamou uma consulta para retornar entidades
- Chamou uma consulta para outros tipos
- Denominada consulta de atualização
- Consultas SQL examinadas
- Criou uma camada de abstração
- Aprendeu sobre a detecção automática de alterações
- Ficou sabendo sobre o código-fonte EF Core e os planos de desenvolvimento
- Aprendeu a usar LINQ dinâmico para simplificar o código
Isso conclui esta série de tutoriais sobre como usar o Entity Framework Core em um aplicativo ASP.NET Core MVC. Esta série trabalhou com um novo banco de dados; uma alternativa é fazer engenharia reversa com um modelo de um banco de dados existente.