Entity Framework Core 5 - Preview 1

O Entity Framework Core 5 ganhou seu Preview 1 em março de 2020, e talvez esta seja uma das versões mais esperadas do ORM da Microsoft. Neste artigo, vamos ver como preparar o ambiente para usar essa prévia e quais são as primeiras novidades que ela traz.

A equipe do EF Core publicou o anúncio oficial no blog do .NET em 16 de março de 2020. Desde então, a comunidade passou a testar os recursos que vão moldar a versão final.

Antes de começar com o Entity Framework Core 5

Esta versão é um preview, ou seja, contém funcionalidades incompletas ou que ainda podem mudar bastante até o lançamento final. Por isso, não recomendamos o uso do Entity Framework Core 5 Preview 1 em produção.

O ideal é criar um projeto separado para testes, sem tocar nas aplicações que já funcionam. Assim, você explora as novidades com tranquilidade e volta para a versão estável quando quiser.

Segundo o anúncio oficial, o EF Core 5.0 Preview 1 tem como alvo o .NET Standard 2.1. Na prática, isso significa que ele também roda sobre o .NET Core 3.1, sem exigir o .NET 5.

Instalando o .NET 5 Preview 1

Para os testes deste artigo, optamos por instalar também a nova versão do SDK, que na época se chamava .NET Core 5. Desse modo, todo o ambiente fica alinhado com a próxima geração da plataforma.

Para isso, siga o processo de instalação do site oficial. Lembre que precisamos do SDK, e não somente do Runtime, porque é o SDK que traz os comandos dotnet new, dotnet add e dotnet build.

Feito isso, use o comando dotnet --list-sdks para visualizar todos os SDKs instalados na máquina:

dotnet --list-sdks
2.2.207 [C:\Program Files\dotnet\sdk\]
3.1.102 [C:\Program Files\dotnet\sdk\]
5.0.100-preview.1.20155.7 [C:\Program Files\dotnet\sdk\]

Em seguida, para saber qual versão do SDK está em uso, basta executar o comando dotnet --version:

dotnet --version
5.0.100-preview.1.20155.7

Voltando para uma versão anterior com global.json

Por padrão, a CLI do .NET usa o SDK mais recente instalado na máquina, mesmo quando ele é uma versão preview. Para fixar outra versão em uma pasta, usamos o arquivo global.json.

Caso você precise voltar para uma versão anterior, pode remover o SDK preview ou executar o seguinte comando:

dotnet new globaljson --sdk-version 3.1.102 --force

Desse modo, a CLI gera um novo arquivo global.json e sobrescreve o anterior com a versão que você especificou no comando. A partir daí, todo comando dotnet executado naquela pasta e nas subpastas usa o SDK fixado.

Vale lembrar que a CLI procura o global.json subindo a hierarquia de pastas a partir do diretório atual. Portanto, o local onde você cria o arquivo define o alcance da configuração.

Instalando o Entity Framework Core 5 Preview 1

Para estes testes, criamos apenas uma aplicação de console com o comando abaixo:

dotnet new console -o EfCoreFive

Em seguida, adicionamos os pacotes do Entity Framework Core 5 com estes comandos:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 5.0.0-preview.2.20120.8
dotnet add package Microsoft.EntityFrameworkCore.Design --version 5.0.0-preview.2.20120.8

O primeiro pacote dá suporte ao SQL Server, mas você pode usar o provider InMemory no lugar dele sem problemas. Além disso, a prévia também trouxe pacotes para SQLite e Azure Cosmos DB.

O segundo pacote adiciona suporte às migrações, então ele não é necessário caso você trabalhe apenas com o banco em memória.

Repare que todos os pacotes seguem o mesmo número de versão, e isso vale para qualquer pacote relacionado que você adicionar. Curiosamente, o número traz "preview.2" no meio, embora corresponda ao Preview 1 anunciado pela equipe.

Instalando o .NET Tools EF

Outra ferramenta que usamos com frequência é o EF Tools, acionado pelo comando dotnet ef, como na criação de migrações. Ele precisa acompanhar a versão dos pacotes do projeto.

Dessa forma, vamos remover o EF Tools anterior e instalar o preview. Você pode repetir o processo inverso quando quiser deixar de usar a versão preview.

dotnet tool uninstall --global dotnet-ef
dotnet tool install --global dotnet-ef --version 5.0.0-preview.2.20120.8

Pronto, isso é tudo de que precisamos para trabalhar com o Entity Framework Core 5 Preview 1. Agora vamos montar um contexto simples para testar as novidades.

Criando um DbContext para testar o Entity Framework Core 5

Para acompanhar os exemplos, basta uma entidade e um contexto. O código abaixo define um cliente com cidade e um DbContext que aponta para o SQL Server local.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string City { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            // ajuste a connection string para o seu ambiente
            .UseSqlServer("Server=localhost;Database=EfCoreFive;Trusted_Connection=True;")
            .LogTo(Console.WriteLine);
}

Repare que o LogTo já aparece na configuração, porque ele é uma das novidades do Entity Framework Core 5. Nas próximas seções, vamos entender cada recurso separadamente.

Se você preferir não instalar o SQL Server, troque o UseSqlServer pelo provider InMemory. Nesse caso, porém, recursos que dependem de SQL, como o ToQueryString, deixam de fazer sentido.

Suporte a log facilitado no Entity Framework Core 5

A primeira novidade anunciada foi um suporte melhor e mais simples ao log das operações que o EF executa na aplicação. Ela lembra o antigo Database.Log do Entity Framework 6, que muita gente sentia falta no EF Core.

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

Com essa opção, as informações aparecem no terminal, incluindo o SQL que o EF Core gera. No entanto, tome cuidado para não exibir informações sensíveis nesse log.

O método LogTo recebe um delegate que aceita uma string, por isso Console.WriteLine funciona diretamente. Da mesma forma, você pode enviar as mensagens para a janela de debug ou para um arquivo, sem instalar nenhum pacote extra no Entity Framework Core 5.

Filtrando o que aparece no log

Existem outras sobrecargas do método que permitem configurar quais e quantas informações serão exibidas. Também é possível aplicar filtros a elas, por nível, por evento ou por categoria.

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

Nesse exemplo, o log mostra apenas mensagens a partir do nível Information. Além disso, a documentação mostra como limitar o log a categorias como DbLoggerCategory.Database, que concentra as interações com o banco.

Filtrar pelas sobrecargas é mais eficiente do que filtrar dentro do delegate. Afinal, quando o filtro descarta uma mensagem, o EF Core nem chega a montá-la.

Dados sensíveis e erros detalhados

Por padrão, o EF Core não inclui valores de dados nas mensagens, porque eles podem ser confidenciais. Quando você precisa desses valores para depurar, a documentação indica o método EnableSensitiveDataLogging.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        // exibe valores de parâmetros, use apenas em desenvolvimento
        .EnableSensitiveDataLogging();

Use essa opção somente em desenvolvimento, já que ela pode expor senhas, documentos e outros dados pessoais. Da mesma forma, o método EnableDetailedErrors acrescenta mais detalhes às exceções, com algum custo de desempenho.

Visualizar a query gerada no Entity Framework Core 5

Para quem nunca confia na query que o EF gera, agora podemos chamar ToQueryString em qualquer consulta LINQ. O resultado é o comando SQL que o banco vai executar.

var query = context.Set<Customer>().Where(c => c.City == city);
Console.WriteLine(query.ToQueryString());

Esse recurso do Entity Framework Core 5 ajuda muito na depuração e na revisão de desempenho. Por exemplo, você consegue conferir se um filtro virou WHERE no banco ou se uma projeção trouxe apenas as colunas necessárias.

Se você quer entender por que algumas consultas viram SQL e outras rodam em memória, leia o artigo sobre IEnumerable vs IQueryable no EF.

Tabelas sem chave no Entity Framework Core 5

Se você já usou o EF alguma vez, principalmente com uma boa orientação a objetos, provavelmente sofreu para mapear objetos que não possuem uma chave. O atributo Keyless chega justamente para indicar que uma classe não tem chave primária.

[Keyless]
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public int Zip { get; set; }
}

Antes do Entity Framework Core 5, o mesmo resultado exigia a chamada HasNoKey() na Fluent API, dentro do OnModelCreating. Agora, a anotação deixa a intenção visível na própria classe.

Contudo, vale entender os limites desse tipo de entidade. O EF Core nunca rastreia mudanças em entidades sem chave, então elas não participam de inserts, updates ou deletes.

Por isso, a documentação recomenda esse recurso para views sem chave primária, tabelas sem chave definida e retornos de consultas SQL. Para objetos de valor que fazem parte de uma entidade, os tipos owned continuam sendo o caminho mais comum.

Alterar connection string dinamicamente

No Entity Framework Core 5, agora é possível alterar dinamicamente a connection string de um DbContext que já passou pela inicialização. Na época do Preview 1, porém, ainda não havia material oficial com exemplos para esse cenário.

Mais tarde, a documentação do EF Core passou a citar o método SetConnectionString, disponível no Database do contexto em providers relacionais. Mesmo assim, a troca só funciona quando a conexão atual não está aberta.

Esse recurso interessa a quem trabalha com cenários multi-tenant, em que cada cliente usa um banco diferente. Se quiser revisar como montar uma connection string, veja o artigo SQL Server: descomplicando as connection strings.

Checagem de enumeradores no Entity Framework Core 5

Durante a geração das migrações, o EF Core passa a criar restrições para enumeradores. Assim, o banco garante que nenhum valor gravado está fora do intervalo do enum.

EnumColumn VARCHAR(10) NOT NULL CHECK (MyEnumColumn IN('Useful', 'Useless', 'Unknown'))

Na prática, essa restrição CHECK gerada pelo Entity Framework Core 5 protege o banco mesmo quando outra aplicação, que não usa o EF, grava dados na mesma tabela. Como se trata de um preview, porém, acompanhe as notas das versões seguintes para confirmar se o comportamento continua igual.

Checagem de banco com IsRelational

Agora podemos usar métodos como IsRelational, IsSqlServer, IsSqlite e IsInMemory para verificar qual banco estamos usando. O destaque do Preview 1 é o IsRelational, que identifica qualquer provider relacional.

if (Database.IsRelational())
{
    // configuração específica para bancos relacionais
}

Essa funcionalidade é muito útil quando você quer executar determinada ação apenas em memória ou apenas em um banco local com SQLite, por exemplo. Também ajuda a manter um mesmo modelo funcionando em testes e em produção.

Outras novidades do Entity Framework Core 5 Preview 1

Além dos recursos acima, o anúncio oficial listou uma série de melhorias menores, mas importantes no dia a dia. Vamos passar por elas rapidamente.

Proxies de rastreamento de mudanças

O EF Core agora consegue gerar proxies em tempo de execução que implementam INotifyPropertyChanging e INotifyPropertyChanged. Com isso, o contexto do Entity Framework Core 5 detecta mudanças automaticamente, sem varrer todas as entidades no SaveChanges.

Debug views aprimoradas

As visualizações de depuração do modelo e do gerenciador de estado ficaram mais completas, com versões resumida e detalhada. Assim, fica mais fácil entender o que o contexto está rastreando em um determinado momento.

Semântica de nulos e propriedades indexadoras

A tradução da semântica de nulos do C# para bancos relacionais ficou mais eficiente, gerando consultas mais enxutas. Além disso, o EF Core passou a mapear propriedades indexadoras do C#, o que abre caminho para os chamados property bags.

Cosmos DB e novas traduções

No Entity Framework Core 5, o provider do Azure Cosmos DB ganhou suporte a concorrência otimista com ETags. Para isso, basta marcar a propriedade com IsEtagConcurrency() na configuração da entidade.

builder.Entity<Customer>().Property(c => c.ETag).IsEtagConcurrency();

Também chegaram novas traduções de consultas, como funções de DateTime no SQL Server, operações com arrays de bytes, o operador Reverse e operadores bit a bit. No Cosmos DB, métodos como Contains, StartsWith e EndsWith passaram a ser traduzidos.

O que esperar da versão final do Entity Framework Core 5

Você pode conferir todas as novidades no post oficial de anúncio do EF Core 5 Preview 1. Além disso, a equipe deixou claro que muitas outras novidades estavam a caminho nas prévias seguintes.

O item mais aguardado pela comunidade era o mapeamento muitos para muitos, o famoso NxN, sem a necessidade de mapear explicitamente a tabela de junção. Esse recurso chegou na versão final do Entity Framework Core 5, lançada junto com o .NET 5 em novembro de 2020.

Se você quer entender a expectativa da comunidade em torno dessas mudanças, leia também o artigo mudanças no EF Core 5. E, para comparar o EF Core com um micro ORM, vale ver o comparativo de performance entre EF Core e Dapper.

Conclusão

O Entity Framework Core 5 Preview 1 trouxe recursos que facilitam muito a vida de quem depura aplicações, como o LogTo e o ToQueryString. Além disso, ele simplificou o mapeamento de entidades sem chave e abriu caminho para cenários com troca dinâmica de conexão.

Como se trata de uma prévia, use essas novidades em projetos de estudo e acompanhe os próximos previews. Vamos aguardar mais demos e, principalmente, o esperado NxN!

FAQ

Posso usar o EF Core 5 Preview 1 em produção?

Não é recomendado, porque a versão preview contém funcionalidades incompletas que podem mudar até o lançamento final.

Preciso do .NET 5 para usar o EF Core 5 Preview 1?

Não necessariamente, pois a prévia tem como alvo o .NET Standard 2.1 e roda sobre o .NET Core 3.1. Neste artigo, optamos por instalar também o SDK do .NET 5 Preview 1.

Para que serve o método ToQueryString?

Ele retorna o comando SQL que uma consulta LINQ vai executar no banco, sem precisar rodar a consulta nem configurar logs.

Qual a diferença entre o atributo Keyless e o HasNoKey?

Os dois marcam uma entidade como sem chave. O [Keyless] é uma anotação na classe, enquanto o HasNoKey() é a configuração equivalente na Fluent API.

Como volto a usar uma versão estável do SDK?

Crie um global.json com dotnet new globaljson --sdk-version informando a versão estável, ou desinstale o SDK preview.