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 estar ciente quando você vai além das noções básicas de 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 retornar outros tipos
- Chamar uma consulta de atualização
- Examinar consultas SQL
- Criar uma camada de abstração
- Saiba mais sobre a Deteçã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 vincular seu 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ê, o que também libera você de ter que escrevê-los você mesmo. Mas há cenários excecionais em que você precisa executar consultas SQL específicas que você criou manualmente. Para esses cenários, a API do 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ê desativar o controle.Use o
Database.ExecuteSqlCommand
para comandos que não sejam de consulta.
Se você precisar executar uma consulta que retorna tipos que não são entidades, poderá usá ADO.NET com a conexão de banco de dados fornecida pelo EF. Os dados retornados não são rastreados pelo contexto do banco de dados, mesmo se você usar esse método para recuperar tipos de entidade.
Como sempre acontece 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 do departamento.
No 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 o separador Departamentos e, em seguida, Detalhes para um dos departamentos.
Chamar uma consulta para retornar 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 inscrição. Você obteve os dados do conjunto de entidades Students (_context.Students
) e usou o 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 ligação à base de dados do EF.
No 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);
}
Adicione uma instrução using:
using System.Data.Common;
Execute o aplicativo e vá para a página Sobre. Ele exibe os mesmos dados que exibia antes.
Executar uma consulta de atualização
Suponha que os administradores da Universidade Contoso queiram realizar alterações globais no banco de dados, como alterar o número de créditos de cada curso. Se a universidade tem um grande número de cursos, seria ineficiente recuperá-los todos como entidades e mudá-los individualmente. Nesta seção, você implementará uma página da Web que permite ao usuário especificar um fator pelo qual 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 ilustração:
No CoursesController.cs
, adicione os 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 a exibição exibe uma caixa de texto vazia e um botão de envio, conforme mostrado na ilustração anterior.
Quando o botão Update é pressionado, o método HttpPost é chamado, e o multiplicador recebe 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 vista obtém um valor RowsAffected
, exibe o número de linhas atualizadas.
No Explorador de Soluções , clique com o botão direito do rato na pasta Exibições/Cursos e, em seguida, clique em Adicionar > Novo Item.
Na caixa de diálogo Adicionar Novo Item, clique em ASP.NET Core sob Instalado no painel esquerdo, clique em Razor Vistae nomeie a nova vista UpdateCourseCredits.cshtml
.
No 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, em seguida, adicionando "/UpdateCourseCredits" ao final do URL na barra de endereços do navegador (por exemplo: http://localhost:5813/Courses/UpdateCourseCredits
). Insira um número na caixa de texto:
Clique Atualizar. Você vê o número de linhas afetadas:
Clique Voltar à Lista para ver a lista de cursos com o número revisado de créditos.
Observe que o código de produção garantiria que as atualizações sempre resultassem em dados válidos. O código simplificado mostrado aqui poderia 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 poderiam causar resultados inesperados em outras partes do sistema que assumem que o número de créditos é 5 ou menos.
Para obter mais informações sobre consultas SQL brutas, consulte 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 interna 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 SQL.
Abra o 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 vá para a página Detalhes de um aluno.
Vá para a janela Output 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ê notará algo aqui que pode surpreendê-lo: o SQL seleciona até 2 linhas (TOP(2)
) da tabela Pessoa. O método SingleOrDefaultAsync
não resulta numa única linha no servidor. Aqui está o porquê:
- Se a consulta retornaria várias linhas, o método retornará null.
- 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 Output. É apenas uma maneira conveniente de parar o registro no ponto em que você deseja examinar a saída. Se não fizeres isso, o registo continuará e terás de retroceder para encontrar as partes que te interessam.
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 de 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 teste de unidade automatizado ou o desenvolvimento orientado a testes (TDD). No entanto, escrever código adicional para implementar esses padrões nem sempre é a melhor escolha para aplicativos que usam 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 EF.
O EF inclui recursos para implementar 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 do 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 testes. Para obter mais informações, consulte Test with InMemory.
Deteçã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 deteção automática de alterações são os seguintes:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Entries
Se você estiver rastreando 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 deteçã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
A fonte principal do Entity Framework está em https://github.com/dotnet/efcore. O repositório EF Core contém compilações noturnas, acompanhamento de problemas, especificações de recursos, notas de reunião de design e o roteiro para futurosde desenvolvimento. Você pode arquivar ou encontrar erros, e contribuir.
Embora o código-fonte esteja aberto, o Entity Framework Core é totalmente suportado 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.
Engenharia reversa a partir de 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.
Use o LINQ dinâmico para simplificar o código
O terceiro tutorial desta série mostra como escrever código LINQ codificando nomes de coluna numa instrução switch
. Com duas colunas para escolher, isso funciona bem, mas se você tiver muitas colunas, o código pode ficar detalhado. 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));
}
Agradecimentos
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 erros comuns
ContosoUniversity.dll está utilizado 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á a ser utilizado por outro processo.
Solução:
Pare o site no IIS Express. Vá para a Bandeja de Sistema do Windows, localize o IIS Express e clique com o botão direito do mouse em seu ícone, selecione o site da Universidade Contoso e clique em Parar Site.
Scaffold de migração feito sem código nos métodos Up e Down
Possível causa:
Os comandos da EF CLI não fecham e salvam automaticamente os arquivos de código. 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 ao executar a atualização do banco de dados
É possível obter outros erros ao fazer alterações de esquema em um banco de dados que tem dados existentes. Se você receber erros de migração que não pode 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 tem muito mais probabilidade de ser concluído sem erros.
A abordagem mais simples é renomear o banco de dados em appsettings.json
. Da 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 ao localizar o servidor/instância especificada)
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 de novo com um novo banco de dados.
Obter o código
Descarregue ou veja a candidatura preenchida.
Recursos adicionais
Para obter mais informações sobre EF Core, consulte a documentação do Entity Framework Core. Também está disponível um livro: Entity Framework Core in Action.
Para obter informações sobre como implantar um aplicativo Web, consulte Host e implantar ASP.NET Core.
Para obter informações sobre outros tópicos relacionados ao MVC ASP.NET Core, como autenticação e autorização, consulte Visão geral do ASP.NET Core.
Para obter orientação sobre como criar um aplicativo ASP.NET Core confiável, seguro, com desempenho, testável e escalável, consulte Padrões de aplicativos Web corporativos. Está disponível um aplicativo Web de exemplo completo com qualidade de produção que implementa os padrões.
Próximos passos
Neste tutorial, você:
- Consultas SQL brutas executadas
- Executar uma consulta para retornar entidades
- Chamada de consulta para retornar outros tipos
- Chamada de consulta de atualização
- Consultas SQL examinadas
- Criou uma camada de abstração
- Aprendeu sobre a deteção automática de alterações
- Saiba mais sobre EF Core código-fonte e planos de desenvolvimento
- Aprendeu a usar o 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 MVC ASP.NET Core. Esta série trabalhou com uma nova base de dados; uma alternativa é fazer engenharia reversa de um modelo a partir de uma base de dados existente.