Atuando na correção de diversas falhas relatadas no Entity Framework Core, a Microsoft está incluindo 40 grandes mudanças ao EF Core 3. A lista completa de mudanças está no Microsoft Docs.
Execução de Query no Cliente
Para trabalhar com as limitações do EF Core na geração de queries SQL, as queries são parcialmente executadas, por padrão, no lado do cliente. Isso significa que para queries LINQ que não podem ser traduzidas em SQL; tabelas serão carregadas do banco e operações restantes serão executadas em memória. Até a versão 2.1, mesmo o GROUP BY era executado no lado do cliente.
A parte ruim desse comportamento é que um problema em uma cláusula Where()
poderia fazer com que o EF Core carregasse a tabela inteira em memória. As pessoas desenvolvedoras de software também reportaram que em casos onde não foi possível gerar uma subquery relacionada, seriam executadas centenas ou milhares de subqueries.
Com esse novo padrão, apenas o Select()
final pode ser executado no lado do cliente. Se o EF Core não pode gerar o SQL correto ele retornará uma exceção. Podemos sobrescrever esse comportamento, mas a Microsoft prefere que notifiquemos um bug.
Há um ticket com mais informações chamado: Outline guiding principles and decide on direction for queries in 3.0.
SQL Parametrizada e Interpolada
As we reported in 2017, EF Core's string interpolation feature raised many concerns. With this feature, interpolated strings can be automatically converted into parameterized SQL, but only if they are not stored in a temporary variable first.
Reportamos em 2017, que a interpolação de string no EF Core gerava algumas preocupações. Com essa feature, strings interpoladas podem ser automaticamente convertidas em SQLs parametrizadas, mas somente se antes não estiverem contidas em uma variável temporária.
v1 = context.Customers.FromSql($"SELECT * FROM Customers WHERE City = {city}");
var sql = $"SELECT * FROM Customers WHERE City = {city}";
v2 = context.Customers.FromSql(sql);
No exemplo acima, a v1 é corretamente parametrizada, enquanto na v2 expõe uma vulnerabilidade de SQL injection.
Para remover essa pedra no caminho, a função FromSql
será removida. Ela será substituída por FromSqlRaw
e FromSqlInterpolated
para tornar a intenção mais clara.
Chaves Temporárias
Para rastrear novas entidades, o EF Core geralmente cria chaves primárias temporárias. Elas podem ser salvas em propriedades de chave (ex.: CustomerKey ou OrderId) como um número negativo. Na teoria, elas poderiam ser substituídas por chaves reais quando geradas. Infelizmente, isso tem muitos efeitos colaterais negativos, como a exibição na UI dessas chaves falsas ou mesmo serem persistidas na base.
O EF Core 3 irá mover esse rastreamento de informação para dentro da entidade, deixando a propriedade chave somente com dados limpos.
Tempo de Exclusão em Cascata
A exclusão em cascata irá ocorrer imediatamente à chamada de método, como o context.Remove()
. Anteriormente, o EF Core não podia calcular quais registros filho seriam excluídos até a chamada do SaveChanges
. Essa alteração afeta código de log para registros que serão alterados/excluídos.
Podemos restaurar o comportamento anterior configurando as opções CascadeDeleteTiming
e DeleteOrphansTiming
para CascadeTiming.OnSaveChanges
.
Query Types são Obsoletas
Diferente das versões anteriores do Entity Framework, o EF Core foi desenhado para trabalhar somente com tabelas que possuem uma chave primária. Isso é problemático, pois os resultados de uma view ou stored procedure não possuem chaves primárias. No EF Core 2.1, foi introduzido o conceito de query types.
Essencialmente, as query types usam um modelo de objeto paralelo. Ao invés de DbSet<T>
, definimos DbQuery<T>
. Elas são registradas usando ModelBuilder.Query<>()
no lugar de ModelBuilder.Entity<>()
e são invocadas usando DbContext.Query<>()
ao invés de DbContext.Set<>()
.
Muitas pessoas reclamaram sobre a confusão desnecessária entre as duas, então decidiram por removê-la. A partir do EF Core 3, o modelo DbSet será usado para todas as fontes de dados. Se não houver chave primária, poderemos simplesmente anotar com um .HasNoKey()
ao registrar a entidade.
Getters e Setters das Propriedades são Ignoradas
No passado, o EF Core poderia invocar um getter ou setter de uma propriedade exceto ao materializar os resultados de uma query. No caso da query, o EF Core podia ignorar a propriedade e escrever diretamente sob o campo subjacente caso o conhecesse.
Com essa mudança, o campo subjacente será sempre usado quando conhecido, independentemente de como o EF Core está interagindo com ele. A vantagem é prevenir que lógica de negócio seja acidentalmente disparada.
A desvantagem com a lógica de negócio, como atualizar campos calculados, é que não serão disparadas. Portanto, precisamos modificar a configuração UsePropertyAccessMode
para termos o comportamento que queremos.
Detecção de Campos Subjacentes
Para detectar campos subjacentes das alterações acima há situações em que o código é ambíguo. Antes o EF Core podia só supor qual campo deveria preencher baseado num sistema de ranking interno.
No EF Core 3, qualquer ambiguidade irá disparar uma exceção. A indicação de qual campo usar no modelo construído terá de ser feita manualmente.
ValueTask Substitui Task
Tornar Task um objeto é considerado um dos grandes erros do .NET. Aceitável para tasks longas, geralmente acaba criando pressão excessiva na memória quando muitas tasks de vida curta são criadas. Por isso, da introdução do ValueTask
, uma alternativa baseada em struct.
Para suportar esse novo tipo, muitos métodos como o FindAsync
e o NextValueAsync
foram atualizados para retornar uma ValueTask
ao invés de uma Task
. Isso afeta não somente código que espera por um resultado, mas se fizermos qualquer outra coisa com a task precisamos chamar AsTask()
para mudar de uma ValueTask
para uma Task
.
Simplificação de IEntityType e IProperty
Essas interfaces terão cinco métodos removidos, e serão substituídos por métodos de extensão. A justificativa é que isso tornará fácil a implementação da interface quando pequenas.