Compartilhar via


Novidades no EF Core 5.0

A lista a seguir inclui os principais novos recursos no EF Core 5.0. Para conferir a lista completa de problemas da versão, consulte nosso rastreador de problemas.

Por ser uma versão principal, o EF Core 5.0 também contém várias alterações interruptivas, que são melhorias de API ou alterações de comportamento que podem ter um impacto negativo nos aplicativos existentes.

Muitos para muitos

O EF Core 5.0 é compatível com relações muitos para muitos sem mapeamento explícito da tabela de junção.

Por exemplo, considere estes tipos de entidades:

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public int Id { get; set; }
    public string Text { get; set; }
    public ICollection<Post> Posts { get; set; }
}

Por convenção, o EF Core 5.0 reconhece isso como uma relação muitos para muitos e cria automaticamente uma tabela de junção PostTag no banco de dados. Os dados podem ser consultados e atualizados sem referência explícita à tabela de junção, o que simplifica consideravelmente o código. A tabela de junção ainda pode ser personalizada e consultada explicitamente, se necessário.

Para saber mais, confira a documentação completa sobre as relações muitos para muitos.

Dividir consultas

A partir do EF Core 3.0, o EF Core sempre gera uma única consulta SQL para cada consulta LINQ. Isso garante a consistência dos dados retornados, de acordo com as restrições do modo de transação em uso. No entanto, esse processo pode ser muito lento quando a consulta usa Include ou uma projeção para retornar várias coleções relacionadas.

O EF Core 5.0 agora permite que uma única consulta LINQ, incluindo as coleções relacionadas, seja dividida em várias consultas SQL. Isso pode melhorar significativamente o desempenho, mas pode resultar em inconsistência nos resultados retornados quando dados são alterados entre as duas consultas. Embora transações serializáveis ou de instantâneo possam ser usadas para reduzir o problema e obter consistência em consultas divididas, isso pode resultar em outros custos de desempenho e diferenças de comportamento.

Por exemplo, considere uma consulta que efetua pull de dois níveis de coleções relacionadas usando Include:

var artists = context.Artists
    .Include(e => e.Albums)
    .ToList();

Por padrão, o EF Core gera o seguinte SQL quando o provedor SQLite é usado:

SELECT a."Id", a."Name", a0."Id", a0."ArtistId", a0."Title"
FROM "Artists" AS a
LEFT JOIN "Album" AS a0 ON a."Id" = a0."ArtistId"
ORDER BY a."Id", a0."Id"

Com as consultas divididas, o seguinte SQL é gerado:

SELECT a."Id", a."Name"
FROM "Artists" AS a
ORDER BY a."Id"

SELECT a0."Id", a0."ArtistId", a0."Title", a."Id"
FROM "Artists" AS a
INNER JOIN "Album" AS a0 ON a."Id" = a0."ArtistId"
ORDER BY a."Id"

As consultas divididas podem ser habilitadas colocando o novo operador AsSplitQuery em qualquer lugar na consulta LINQ ou globalmente, no OnConfiguring do modelo. Para saber mais, confira a documentação completa sobre consultas divididas.

Registro em log simples e diagnóstico aprimorado

O EF Core 5.0 oferece uma maneira simples de configurar o registro em log por meio do novo método LogTo. O seguinte código fará com que as mensagens de log sejam gravadas no console, incluindo todos os SQL gerados pelo EF Core:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

Além disso, agora é possível chamar ToQueryString em qualquer consulta LINQ, recuperando o SQL que ela executaria:

Console.WriteLine(
    ctx.Artists
    .Where(a => a.Name == "Pink Floyd")
    .ToQueryString());

Por fim, vários tipos do EF Core foram ajustados com uma propriedade DebugView avançada que fornece uma exibição detalhada dos elementos internos. Por exemplo, é possível consultar ChangeTracker.DebugView para analisar exatamente quais entidades estão sendo rastreadas em um determinado momento.

Para saber mais, confira a documentação sobre registro em log e interceptação.

Inclusão filtrada

O método Include agora dá suporte à filtragem das entidades incluídas:

var blogs = context.Blogs
    .Include(e => e.Posts.Where(p => p.Title.Contains("Cheese")))
    .ToList();

Essa consulta retornará blogs com cada postagem associada, mas somente quando o título da postagem contiver "queijo".

Para saber mais, confira a documentação completa sobre inclusões filtradas.

Mapeamento TPT (tabela por tipo)

Por padrão, o EF Core mapeia uma hierarquia de herança de tipos .NET para uma única tabela de banco de dados. Isso é conhecido como mapeamento TPH (tabela por hierarquia). O EF Core 5.0 também permite mapear cada tipo .NET em uma hierarquia de herança para uma tabela de banco de dados diferente. Esse processo é conhecido como mapeamento TPT (tabela por tipo).

Por exemplo, considere este modelo com uma hierarquia mapeada:

public class Animal
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Cat : Animal
{
    public string EducationLevel { get; set; }
}

public class Dog : Animal
{
    public string FavoriteToy { get; set; }
}

Com TPT, uma tabela de banco de dados é criada para cada tipo na hierarquia:

CREATE TABLE [Animals] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NULL,
    CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);

CREATE TABLE [Cats] (
    [Id] int NOT NULL,
    [EducationLevel] nvarchar(max) NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Cats_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE NO ACTION,
);

CREATE TABLE [Dogs] (
    [Id] int NOT NULL,
    [FavoriteToy] nvarchar(max) NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Dogs_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE NO ACTION,
);

Para saber mais, confira a documentação completa sobre TPT.

Mapeamento flexível de entidades

Os tipos de entidade são normalmente mapeados para tabelas ou exibições, de modo que o EF Core extrai o conteúdo da tabela ou da exibição ao consultar o tipo. O EF Core 5.0 inclui opções de mapeamento adicionais, em que uma entidade pode ser mapeada para uma consulta SQL (chamada de "consulta de definição") ou para uma TVF (função com valor de tabela):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>().ToSqlQuery(
        @"SELECT Id, Name, Category, BlogId FROM posts
          UNION ALL
          SELECT Id, Name, ""Legacy"", BlogId from legacy_posts");

    modelBuilder.Entity<Blog>().ToFunction("BlogsReturningFunction");
}

As funções com valor de tabela também podem ser mapeadas para um método .NET em vez de para DbSet, o que permite a transmissão dos parâmetros. Esse mapeamento pode ser configurado com HasDbFunction.

Por fim, agora é possível mapear uma entidade para uma exibição ao realizar consultas (ou para uma função ou consulta de definição), mas mapeá-la para uma tabela ao realizar atualizações:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Blog>()
        .ToTable("Blogs")
        .ToView("BlogsView");
}

Pacotes de propriedades e tipos de entidade de tipo compartilhado

O EF Core 5.0 permite que o mesmo tipo CLR seja mapeado para vários tipos de entidade diferentes. Esses tipos são conhecidos como tipos de entidade de tipo compartilhado. Embora qualquer tipo CLR possa ser usado com esse recurso, o .NET Dictionary oferece um caso de uso particularmente convincente, que é chamado de "pacotes de propriedades":

public class ProductsContext : DbContext
{
    public DbSet<Dictionary<string, object>> Products => Set<Dictionary<string, object>>("Product");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Product", b =>
        {
            b.IndexerProperty<int>("Id");
            b.IndexerProperty<string>("Name").IsRequired();
            b.IndexerProperty<decimal>("Price");
        });
    }
}

Essas entidades podem ser consultadas e atualizadas da mesma forma que os tipos de entidade normais, com o próprio tipo CLR dedicado. Saiba mais na documentação sobre pacotes de propriedades.

Dependentes 1:1 necessários

No EF Core 3.1, a extremidade dependente de uma relação um para um sempre foi considerada opcional. Isso era mais aparente ao usar entidades próprias, pois todas as colunas delas eram criadas como anuláveis no banco de dados, mesmo que fossem configuradas conforme necessário no modelo.

No EF Core 5.0, uma navegação para uma entidade própria pode ser configurada como um dependente necessário. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>(b =>
    {
        b.OwnsOne(e => e.HomeAddress,
            b =>
            {
                b.Property(e => e.City).IsRequired();
                b.Property(e => e.Postcode).IsRequired();
            });
        b.Navigation(e => e.HomeAddress).IsRequired();
    });
}

DbContextFactory

O EF Core 5.0 oferece AddDbContextFactory e AddPooledDbContextFactory para registrar um alocador a fim de criar instâncias DbContext no contêiner de ID (injeção de dependência) do aplicativo. Isso pode ser útil quando o código do aplicativo precisa criar e descartar instâncias de contexto manualmente.

services.AddDbContextFactory<SomeDbContext>(b =>
    b.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));

Aqui, é possível injetar serviços de aplicativo, como os controladores do ASP.NET Core, com IDbContextFactory<TContext> e usá-los para criar instâncias de contexto:

public class MyController : Controller
{
    private readonly IDbContextFactory<SomeDbContext> _contextFactory;

    public MyController(IDbContextFactory<SomeDbContext> contextFactory)
        => _contextFactory = contextFactory;

    public void DoSomeThing()
    {
        using (var context = _contextFactory.CreateDbContext())
        {
            // ...
        }
    }
}

Para saber mais, confira a documentação completa sobre DbContextFactory.

Recompilações de tabela do SQLite

Em comparação com outros bancos de dados, o SQLite é relativamente limitado em seus recursos de manipulação de esquema. Por exemplo, não há suporte para remover uma coluna de uma tabela existente. O EF Core 5.0 lida com essas limitações criando automaticamente uma tabela, copiando os dados da tabela antiga, removendo a tabela antiga e renomeando a nova. Isso "recompila" a tabela e permite que as operações de migração anteriormente não compatíveis sejam aplicadas com segurança.

Para obter detalhes sobre quais operações de migração têm suporte atualmente por meio de recompilações de tabela, confira esta página da documentação.

Ordenações de banco de dados

O EF Core 5.0 oferece suporte para a especificação de agrupamentos de texto no nível do banco de dados, da coluna ou da consulta. Isso permite que a distinção entre maiúsculas e minúsculas e outros aspectos textuais sejam configurados de maneira flexível, sem comprometer o desempenho da consulta.

Por exemplo, o seguinte código definirá a coluna Name para diferenciar maiúsculas de minúsculas no SQL Server, e todos os índices criados na coluna funcionarão adequadamente:

modelBuilder
    .Entity<User>()
    .Property(e => e.Name)
    .UseCollation("SQL_Latin1_General_CP1_CS_AS");

Para saber mais, confira a documentação completa sobre agrupamentos e diferenciação entre maiúsculas e minúsculas.

Contadores de eventos

O EF Core 5.0 expõe contadores de eventos que podem ser usados para acompanhar o desempenho do aplicativo e detectar várias anomalias. Basta anexá-los a um processo que executa o EF com a ferramenta dotnet-counters:

> dotnet counters monitor Microsoft.EntityFrameworkCore -p 49496

[Microsoft.EntityFrameworkCore]
    Active DbContexts                                               1
    Execution Strategy Operation Failures (Count / 1 sec)           0
    Execution Strategy Operation Failures (Total)                   0
    Optimistic Concurrency Failures (Count / 1 sec)                 0
    Optimistic Concurrency Failures (Total)                         0
    Queries (Count / 1 sec)                                     1,755
    Queries (Total)                                            98,402
    Query Cache Hit Rate (%)                                      100
    SaveChanges (Count / 1 sec)                                     0
    SaveChanges (Total)                                             1

Para saber mais, confira a documentação completa sobre contadores de eventos.

Outros recursos

Criação de modelo

  • As APIs de criação de modelo foram introduzidas para facilitar a configuração dos comparadores de valor.
  • As colunas computadas agora podem ser configuradas como armazenadas ou virtuais.
  • A precisão e a escala agora podem ser configuradas por meio da API fluente.
  • Novas APIs de criação de modelo foram introduzidas para propriedades de navegação.
  • Novas APIs de criação de modelo foram introduzidas para campos, assim como para propriedades.
  • Os tipos PhysicalAddress e IPAddress do .NET agora podem ser mapeados para colunas de cadeia de caracteres de banco de dados.
  • Um campo de suporte agora pode ser configurado por meio do novo atributo [BackingField].
  • Agora são permitidos campos de suporte anuláveis, o que fornece melhor suporte para padrões gerados por armazenamento, em que o padrão CLR não é um bom valor de sentinela (bool notável).
  • É possível usar um novo atributo [Index] em um tipo de entidade para especificar um índice, em vez de usar a API fluente.
  • É possível usar um novo atributo [Keyless] para configurar um tipo de entidade como sem chave.
  • Por padrão, o EF Core agora considera discriminadores como completos, o que significa que ele espera nunca ver valores discriminadores não configurados pelo aplicativo no modelo. Isso permite alguns aprimoramentos de desempenho e pode ser desabilitado quando há possibilidade de a coluna discriminadora conter valores desconhecidos.

Query

  • As exceções de falha de tradução de consulta agora contêm motivos mais explícitos sobre os motivos da falha, para ajudar na identificação do problema.
  • As consultas sem rastreamento agora podem executar a resolução de identidade, a fim de evitar que várias instâncias de entidade sejam retornadas para o mesmo objeto de banco de dados.
  • Adição de suporte para GroupBy com agregações condicionais (por exemplo, GroupBy(o => o.OrderDate).Select(g => g.Count(i => i.OrderDate != null))).
  • Adição de suporte para a tradução do operador Distinct em elementos de grupo antes da agregação.
  • Tradução de Reverse.
  • Aprimoramento da tradução de DateTime para SQL Server (por exemplo, DateDiffWeek e DateFromParts).
  • Tradução de novos métodos em matrizes de bytes (por exemplo, Contains, Length e SequenceEqual).
  • Tradução de alguns operadores bit a bit adicionais, como o complemento de dois.
  • Tradução de FirstOrDefault em cadeias de caracteres.
  • Aprimoramento da tradução de consultas em semânticas nulas, o que resulta em consultas mais ajustadas e eficientes.
  • Agora é possível anotar as funções mapeadas pelo usuário para controlar o tratamento simplificado de nulo, o que também resulta em consultas mais ajustadas e eficientes.
  • Agora, SQLs que contêm blocos em maiúsculas são consideravelmente mais concisos.
  • A função DATALENGTH do SQL Server agora pode ser chamada em consultas por meio do novo método EF.Functions.DataLength.
  • EnableDetailedErrors adiciona detalhes adicionais a exceções.

Salvando

  • Interceptação e eventos de SaveChanges.
  • Fornecimento de APIs para controlar os pontos de salvamento de transação. Além disso, o EF Core criará automaticamente um ponto de salvamento quando SaveChanges for chamado e uma transação já estiver em andamento e fará a reversão para ele em caso de falha.
  • Uma ID de transação pode ser definida explicitamente pelo aplicativo, permitindo uma correlação mais fácil de eventos de transação no registro em log e em outros lugares.
  • Alteração do tamanho de lote máximo padrão do SQL Server para 42 com base em uma análise do desempenho de envio em lote.

Migrações e scaffolding

  • As tabelas agora podem ser excluídas das migrações.
  • Um novo comando dotnet ef migrations list agora mostra quais migrações ainda não foram aplicadas ao banco de dados (Get-Migration faz o mesmo no console do Gerenciamento de Pacotes).
  • Os scripts de migrações agora contêm instruções de transação, quando apropriado, para melhorar o tratamento dos casos em que o aplicativo de migração falha.
  • As colunas de classes base não mapeadas agora são ordenadas após outras colunas de tipos de entidade mapeados. Observe que isso afeta apenas as tabelas recém-criadas, pois a ordem das colunas em tabelas existentes permanece inalterada.
  • A geração de migração agora reconhece quando a migração que está sendo gerada é idempotente e se a saída será executada imediatamente ou gerada como um script.
  • Adição de novos parâmetros de linha de comando para especificar namespaces em Migrações e scaffolding.
  • O comando dotnet ef database update agora aceita um novo parâmetro --connection para a especificação da cadeia de conexão.
  • O scaffolding de bancos de dados existentes agora singulariza os nomes de tabela, de modo que as tabelas nomeadas People e Addresses passarão por scaffolding para tipos de entidade nomeados Person e Address. Os nomes originais do banco de dados ainda podem ser preservados.
  • A nova opção --no-onconfiguring pode instruir o EF Core a excluir OnConfiguring durante o scaffolding de um modelo.

Azure Cosmos DB

SQLite

  • Agora há suporte para colunas computadas.
  • A recuperação de dados binários e de cadeia de caracteres com GetBytes, GetChars e GetTextReader agora é mais eficiente e faz uso de SqliteBlob e fluxos.
  • A inicialização de SqliteConnection agora é lenta.

Outro

  • É possível gerar proxies de controle de alterações que implementam automaticamente INotifyPropertyChanging e INotifyPropertyChanged. Isso fornece uma abordagem alternativa para o controle de alterações que não examina se há alterações quando SaveChanges é chamado.
  • Uma cadeia de conexão ou DbConnection agora pode ser alterada em um DbContext já inicializado.
  • O novo método ChangeTracker.Clear limpa o DbContext de todas as entidades controladas. Isso normalmente não deve ser necessário ao usar a melhor prática de criar uma nova instância de contexto de curta duração para cada unidade de trabalho. No entanto, se houver a necessidade de redefinir o estado de uma instância DbContext, o uso do novo método Clear() será mais eficiente e eficaz do que desanexar todas as entidades em massa.
  • As ferramentas de linha de comando do EF Core agora configuram automaticamente as variáveis de ambiente ASPNETCORE_ENVIRONMENTandDOTNET_ENVIRONMENT para "Desenvolvimento". Isso une a experiência de uso do host genérico com a experiência do ASP.NET Core durante o desenvolvimento.
  • Argumentos de linha de comando personalizados podem ser transmitidos para IDesignTimeDbContextFactory<TContext>, o que permite que os aplicativos controlem como o contexto é criado e inicializado.
  • O fator de preenchimento de índice agora pode ser configurado no SQL Server.
  • A nova propriedade IsRelational pode ser usada para distinguir entre o uso de um provedor relacional e o uso de um provedor não relacional (como na memória).