ASP.NET Core Blazor com o Entity Framework Core (EF Core)
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
Este artigo explica como usar o Entity Framework Core (EF Core) em aplicativos Blazor do servidor.
O lado do servidor Blazor é uma estrutura do aplicativo com estado. O aplicativo mantém uma conexão contínua com o servidor e o estado do usuário é mantido na memória do servidor em um circuito. Um exemplo do estado do usuário são os dados retidos nas instâncias de serviço de DI (injeção de dependência) que têm como escopo o circuito. O modelo de aplicativo exclusivo que Blazor fornece/requer uma abordagem especial para o uso do Entity Framework Core.
Observação
Este artigo aborda EF Core em aplicativos Blazor do servidor. Blazor WebAssembly os aplicativos são executados em uma área restrita WebAssembly que impede a maioria das conexões de banco de dados diretas. A execução de EF Core em Blazor WebAssembly não faz parte do escopo deste artigo.
Essas diretrizes se aplicam a componentes que adotam a SSR interativa (renderização interativa do lado do servidor) em um Blazor Web App.
Essas diretrizes se aplicam ao projeto Server
de uma solução Blazor WebAssembly hospedada ou de um aplicativo Blazor Server.
Fluxo de autenticação seguro necessário para aplicativos de produção
Este artigo refere-se ao uso de um banco de dados local que não requer autenticação de usuário. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos Blazor de teste e produção implantados, consulte os artigos no nó BlazorSegurança e Identity.
Para serviços do Microsoft Azure, recomendamos o uso de identidades gerenciadas. As identidades gerenciadas autenticam de maneira segura para serviços do Azure sem armazenar credenciais no código do aplicativo. Para saber mais, consulte os recursos a seguir:
- O que são identidades gerenciadas para recursos do Azure? (Documentação do Microsoft Entra)
- Documentação dos serviços do Azure
Tutorial Criar um Blazor aplicativo de banco de dados de filmes
Para obter uma experiência de tutorial para criar um aplicativo que usa EF Core para operações de banco de dados, consulte Criar um aplicativo de banco de dados de filme Blazor (Visão geral). O tutorial mostra como criar um Blazor Web App que pode exibir e gerenciar filmes em um banco de dados de filmes.
Acesso ao banco de dados
EF Core depende de um DbContext como meio de configuração do acesso ao banco de dados e atuar como uma unidade de trabalho. EF Core fornece a extensão AddDbContext para aplicativos ASP.NET Core que registram o contexto como um serviço com escopo. Em aplicativos Blazor do servidor, os registros de serviço com escopo podem ser problemáticos porque a instância é compartilhada entre componentes dentro do circuito do usuário. DbContext não é thread-safe e não foi projetado para uso simultâneo. Os tempos de vida existentes são inadequados pelos motivos a seguir:
- Singleton compartilha o estado com todos os usuários do aplicativo e acarreta uso simultâneo inadequado.
- Com escopo (o padrão) representa um problema semelhante entre componentes para o mesmo usuário.
- Resultados transitórios em uma nova instância por solicitação, mas, a possibilidade de longa duração dos componentes resulta em um contexto de vida mais longa que o esperado.
As recomendações a seguir foram projetadas para fornecer uma abordagem consistente ao usar EF Core em aplicativos Blazor do servidor.
Considere usar um único contexto por operação. O contexto foi projetado para instanciação rápida e de baixa sobrecarga:
using var context = new ProductsDatabaseContext(); return await context.Products.ToListAsync();
Use um sinalizador para impedir várias operações simultâneas:
if (Loading) { return; } try { Loading = true; ... } finally { Loading = false; }
Coloque as operações de banco de dados após a linha
Loading = true;
no blocotry
.O acesso thread-safe não é uma preocupação. então o carregamento da lógica não requer o bloqueio de registros de banco de dados. A lógica de carregamento é usada para desabilitar controles de interface do usuário para que os usuários não selecionem botões ou atualizem campos inadvertidamente durante a busca por dados.
Se houver alguma chance de que vários threads possam acessar o mesmo bloco de código, injete um alocador e crie uma nova instância por operação. Caso contrário, injetar e usar o contexto costuma ser suficiente.
Para operações de vida mais longa que aproveitam o acompanhamento de alterações EF Core's ou o controle de simultaneidade, crie o escopo do contexto para o tempo de vida do componente.
Novas instâncias DbContext
A maneira mais rápida de criar uma nova instância DbContext é usar new
para criar uma nova instância. No entanto, há cenários que exigem a resolução de dependências adicionais:
- Uso de
DbContextOptions
para configurar o contexto. - Uso de uma cadeia de conexão por DbContext, como quando você usa o modelo do Identity ASP.NET Core. Para obter mais informações, consulte Multilocação (documentaçãoEF Core).
Aviso
Não armazene segredos de aplicativo, cadeias de conexão, credenciais, senhas, PINs (números de identificação pessoal), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparo e produção, o código do lado do Blazor servidor e as APIs Web devem usar fluxos de autenticação seguros que evitam a manutenção de credenciais no código do projeto ou nos arquivos de configuração. Fora dos testes de desenvolvimento local, recomendamos evitar o uso de variáveis de ambiente para armazenar dados confidenciais, pois as variáveis de ambiente não são a abordagem mais segura. Para testes de desenvolvimento local, a ferramenta Gerenciador de segredos é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter dados e credenciais confidenciais com segurança.
A abordagem recomendada para criar um novo DbContext com dependências é o uso de um alocador. EF Core 5.0 ou posterior fornece um alocador interno para a criação de novos contextos.
Nas versões do .NET anteriores à 5.0, use o seguinte DbContextFactory
:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace BlazorServerDbContextExample.Data
{
public class DbContextFactory<TContext>
: IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public DbContextFactory(IServiceProvider provider)
{
this.provider = provider ?? throw new ArgumentNullException(
$"{nameof(provider)}: You must configure an instance of " +
"IServiceProvider");
}
public TContext CreateDbContext() =>
ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
No alocador anterior:
- ActivatorUtilities.CreateInstance atende a todas as dependências por meio do provedor de serviços.
- IDbContextFactory<TContext> está disponível no EF Core ASP.NET Core 5.0 ou posterior, portanto, a interface anterior só é necessária para ASP.NET Core 3.x.
O exemplo a seguir configura SQLite e habilita o logging de dados em um aplicativo que gerencia contatos. O código usa um método de extensão (AddDbContextFactory) para configurar o alocador de banco de dados para DI (injeção de dependência) e fornecer opções padrão:
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
O alocador é injetado em componentes para criar novas instâncias DbContext.
Analisar o contexto de um banco de dados para um método de componente
O alocador é injetado no componente:
@inject IDbContextFactory<ContactContext> DbFactory
Criar um DbContext para um método usando o alocador (DbFactory
):
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
if (context.Contacts is not null)
{
var contact = await context.Contacts.FirstAsync(...);
if (contact is not null)
{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}
}
Analisar um contexto de banco de dados até o tempo de vida do componente
Talvez seja desejado criar um DbContext que exista durante o tempo de vida de um componente. Isso permite que seja usado como uma unidade de trabalho e aproveitar os recursos internos, como o acompanhamento de alterações e a resolução de simultaneidade.
Implementar IDisposable e injetar o alocador no componente:
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
Estabelecer uma propriedade para DbContext:
private ContactContext? Context { get; set; }
OnInitializedAsync
é substituído para criar o DbContext:
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
}
DbContext é descartado quando o componente é descartado:
public void Dispose() => Context?.Dispose();
Habilite o registro em log de dados confidenciais
EnableSensitiveDataLogging inclui dados do aplicativo em mensagens de exceção e registro em log de estrutura. Os dados registrados podem incluir os valores atribuídos às propriedades de instâncias de entidade e de valores de parâmetro para comandos enviados ao banco de dados. O registro de dados com EnableSensitiveDataLogging é um risco de segurança, pois pode expor senhas e outras informações de identificação pessoal (PII) ao registrar instruções SQL executadas no banco de dados.
Recomenda-se habilitar EnableSensitiveDataLogging apenas para desenvolvimento e teste:
#if DEBUG
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
#else
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
#endif