Partilhar via


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.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como usar EF Core do Entity Framework Core () em aplicativos de Blazor do lado do servidor.

O Blazor do lado do servidor é um framework de aplicação stateful. 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 de estado do usuário são os dados mantidos em injeção de dependência (DI) instâncias de serviço que têm escopo para o circuito. O modelo de aplicativo exclusivo que Blazor fornece requer uma abordagem especial para usar o Entity Framework Core.

Observação

Este artigo aborda EF Core em aplicações de Blazor do lado do servidor. Blazor WebAssembly aplicações são executadas em um ambiente isolado WebAssembly que impede a maioria das conexões diretas ao banco de dados. Executar EF Core em Blazor WebAssembly está além do escopo deste artigo.

Esta orientação se aplica a componentes que adotam renderização interativa do lado do servidor (SSR interativo) em um Blazor Web App.

Esta orientação se aplica ao projeto Server de uma solução de 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 do 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 de teste e produção Blazor implantados, consulte os artigos na segurança Blazore no nó Identity.

Para serviços do Microsoft Azure, recomendamos o uso de identidades gerenciadas . As identidades gerenciadas são autenticadas com segurança nos serviços do Azure sem armazenar credenciais no código do aplicativo. Para obter mais informações, consulte os seguintes recursos:

Criar um tutorial do aplicativo de banco de dados de filmes Blazor

Para obter uma experiência de tutorial criando um aplicativo que usa o EF Core para operações de banco de dados, consulte Criar um aplicativo de banco de dados de filmes 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 à base de dados

EF Core conta com um DbContext como meio para configurar o acesso ao banco de dados e atuar como uma unidade de trabalho. fornece a extensão de para aplicativos ASP.NET Core que registra o contexto como um serviço com escopo . Em aplicações do lado do servidor Blazor, os registos de serviços com escopo podem causar problemas, pois a instância é partilhada entre componentes dentro do circuito de utilizador. DbContext não é thread safe e não foi projetado para uso simultâneo. Os tempos de vida existentes são inadequados pelas seguintes razões:

  • Singleton compartilha o estado entre todos os utilizadores do aplicativo e leva à utilização simultânea inadequada.
  • com escopo (o padrão) cria um problema semelhante entre componentes para o mesmo utilizador.
  • transitório resulta em uma nova instância por solicitação; Mas, como os componentes podem ser de longa duração, isso resulta em um contexto de vida mais longa do que se pode imaginar.

As recomendações a seguir foram projetadas para fornecer uma abordagem consistente ao uso de EF Core em aplicativos Blazor do lado do servidor.

  • Considere o uso de um 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 evitar várias operações simultâneas:

    if (Loading)
    {
        return;
    }
    
    try
    {
        Loading = true;
    
        ...
    }
    finally
    {
        Loading = false;
    }
    

    Coloque as operações do banco de dados após a linha Loading = true; no bloco try.

    A segurança de threads não é uma preocupação, portanto, a lógica de carregamento não requer o bloqueio de registros de banco de dados. A lógica de carregamento é usada para desabilitar os controles da interface do usuário para que os usuários não selecionem inadvertidamente botões ou atualizem campos enquanto os dados são buscados.

  • Se houver alguma chance de que vários threads possam aceder aos mesmos blocos de código, injete uma fábrica e crie uma nova instância por operação. Caso contrário, injetar e usar o contexto geralmente é suficiente.

  • Para operações de vida mais longa que aproveitam a EF Core de controle de alterações do ou controle de simultaneidade, escopo o contexto para o tempo de vida do componente.

Novas instâncias DbContext

A maneira mais rápida de criar uma nova instância de DbContext é usando new para criar uma nova instância. No entanto, há cenários que exigem a resolução de dependências adicionais:

Advertência

Não armazene segredos de aplicativos, cadeias de conexão, credenciais, senhas, números de identificação pessoal (PINs), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparação e produção, o código Blazor do lado do servidor e as APIs da Web devem usar fluxos de autenticação seguros que evitem 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 Secret Manager é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter com segurança dados confidenciais e credenciais.

A abordagem recomendada para criar um novo DbContext com dependências é usar uma fábrica. EF Core 5.0 ou posterior fornece uma fábrica integrada para criar novos contextos.

Em 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);
    }
}

Na fábrica anterior:

O exemplo a seguir configura SQLite e habilita o registro de dados em um aplicativo que gerencia contatos. O código usa um método de extensão (AddDbContextFactory) para configurar a fábrica de banco de dados para DI 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"));

A fábrica é injetada em componentes para criar novas instâncias DbContext.

Definir o âmbito de um contexto de base de dados para um método de componente

A fábrica é inserida no componente.

@inject IDbContextFactory<ContactContext> DbFactory

Crie um DbContext para um método usando a factory (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();
        }
    }
}

Definir o escopo de um contexto de banco de dados para o tempo de vida do componente

Você pode querer criar um DbContext que exista durante o tempo de vida de um componente. Isso permite que você o use como uma unidade de trabalho e aproveite os recursos internos, como controle de alterações e resolução de simultaneidade.

Implemente IDisposable e injete a fábrica no componente:

@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory

Estabeleça uma propriedade para o DbContext:

private ContactContext? Context { get; set; }

OnInitializedAsync é substituído para criar o DbContext:

protected override async Task OnInitializedAsync()
{
    Context = DbFactory.CreateDbContext();
}

O DbContext é eliminado quando o componente é eliminado:

public void Dispose() => Context?.Dispose();

Habilite o registro de dados confidenciais

EnableSensitiveDataLogging inclui dados da aplicação em mensagens de exceção e registo do framework. Os dados registrados podem incluir os valores atribuídos às propriedades de instâncias de entidade e valores de parâmetro para comandos enviados ao banco de dados. O registo de dados com o EnableSensitiveDataLogging é um risco de segurança , dado que pode expor senhas e outras informações pessoalmente identificáveis (PII) ao registar instruções SQL executadas contra a base de dados.

Recomendamos habilitar apenas EnableSensitiveDataLogging para desenvolvimento e testes:

#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

Recursos adicionais