Mudanças significativas no EF Core 9 (EF9)
Esta página documenta as alterações de API e comportamento que têm o potencial de interromper a atualização de aplicativos existentes do EF Core 8 para o EF Core 9. Certifique-se de revisar as alterações importantes anteriores ao atualizar de uma versão anterior do EF Core.
- Mudanças significativas no EF Core 8
- Mudanças significativas no EF Core 7
- Mudanças significativas no EF Core 6
Estrutura de Destino
O EF Core 9 tem como alvo o .NET 8. Isso significa que os aplicativos existentes destinados ao .NET 8 podem continuar a fazê-lo. Os aplicativos destinados a versões mais antigas do .NET, .NET Core e .NET Framework precisarão direcionar o .NET 8 ou o .NET 9 para usar o EF Core 9.
Resumo
Observação
Se estiver a utilizar o Azure Cosmos DB, consulte a secção abaixo sobre alterações significativas no Azure Cosmos DB.
Alterações de alto impacto
É lançada uma exceção ao aplicar migrações se existirem mudanças no modelo pendentes
Problema de rastreamento #33732
Comportamento antigo
Se o modelo tiver alterações pendentes em comparação com a última migração, elas não serão aplicadas com o restante das migrações quando Migrate
for chamada.
Novo comportamento
A partir do EF Core 9.0, se o modelo tiver alterações pendentes em comparação com a última migração, será lançada uma exceção quando dotnet ef database update
, Migrate
ou MigrateAsync
forem chamados:
O modelo de contexto 'DbContext' tem alterações pendentes. Adicione uma nova migração antes de atualizar o banco de dados. Essa exceção pode ser suprimida ou registrada passando a ID do evento 'RelationalEventId.PendingModelChangesWarning' para o método 'ConfigureWarnings' em 'DbContext.OnConfiguring' ou 'AddDbContext'.
Porquê
Esquecer de adicionar uma nova migração depois de fazer alterações no modelo é um erro comum que pode ser difícil de diagnosticar em alguns casos. A nova exceção garante que o modelo do aplicativo corresponda ao banco de dados após a aplicação das migrações.
Atenuações
Existem várias situações comuns em que esta exceção pode ser lançada:
- Não há migrações. Isso é comum quando o banco de dados é atualizado por outros meios.
-
de mitigação: se você não planeja usar migrações para gerenciar o esquema de banco de dados, remova a chamada
Migrate
ouMigrateAsync
, caso contrário, adicione uma migração.
-
de mitigação: se você não planeja usar migrações para gerenciar o esquema de banco de dados, remova a chamada
- Há pelo menos uma migração, mas a captura do modelo está em falta. Isso é comum para migrações criadas manualmente.
- de mitigação: adicione uma nova migração usando ferramentas do EF, isso atualizará a captura de modelo.
- O modelo não foi modificado pelo desenvolvedor, mas é construído de forma não determinística, fazendo com que o EF o detete como modificado. Isso é comum quando
new DateTime()
,DateTime.Now
,DateTime.UtcNow
ouGuid.NewGuid()
são usados em objetos fornecidos aHasData()
.-
Mitigação: Adicione uma nova migração, examine seu conteúdo para localizar a causa e substitua os dados dinâmicos por um valor estático e codificado no modelo. A migração deve ser recriada após a correção do modelo. Se os dados dinâmicos tiverem que ser usados para semeadura, considere usar o novo padrão de semeadura em vez de
HasData()
.
-
Mitigação: Adicione uma nova migração, examine seu conteúdo para localizar a causa e substitua os dados dinâmicos por um valor estático e codificado no modelo. A migração deve ser recriada após a correção do modelo. Se os dados dinâmicos tiverem que ser usados para semeadura, considere usar o novo padrão de semeadura em vez de
- A última migração foi criada para um provedor diferente daquele usado para aplicar as migrações.
- Mitigação: Este é um cenário não suportado. O aviso pode ser suprimido usando o trecho de código abaixo, mas esse cenário provavelmente deixará de funcionar em uma versão futura do EF Core. A solução recomendada é para gerar um conjunto separado de migrações para cada provedor.
- As migrações são geradas ou escolhidas dinamicamente substituindo alguns dos serviços EF.
Mitigação: O aviso é um falso positivo neste caso e deve ser suprimido:
options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))
Se o seu cenário não se enquadrar em nenhum dos casos mencionados acima e adicionar uma nova migração resultar na criação da mesma migração cada vez ou numa migração vazia, e a exceção ainda for lançada, então crie um pequeno projeto de reprodução e partilhá-lo com a equipa do EF num novo assunto.
Alterações de baixo impacto
EF.Functions.Unhex()
agora retorna byte[]?
Comportamento antigo
A função EF.Functions.Unhex()
foi previamente anotada para retornar byte[]
.
Novo comportamento
A partir do EF Core 9.0, o Unhex() agora é anotado para retornar byte[]?
.
Porquê
Unhex()
é traduzido para a função SQLite unhex
, que retorna NULL para entradas inválidas. Como resultado, Unhex()
devolveu null
para esses casos, violando a anotação.
Atenuações
Se você tiver certeza de que o conteúdo de texto passado para Unhex()
representa uma cadeia de caracteres hexadecimal válida, você pode simplesmente adicionar o operador de perdão nulo como uma asserção de que a invocação nunca retornará null:
var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();
Caso contrário, adicione verificações de tempo de execução para "null" no valor de retorno de Unhex().
Aridade dos argumentos de anulabilidade da SqlFunctionExpression validada
Problema de Acompanhamento #33852
Comportamento antigo
Anteriormente, era possível criar um SqlFunctionExpression
com um número diferente de argumentos e argumentos de propagação de anulabilidade.
Novo comportamento
Começando com o EF Core 9.0, o EF agora lança se o número de argumentos e os argumentos de propagação de anulabilidade não corresponderem.
Porquê
Não ter um número correspondente de argumentos e argumentos de propagação de anulabilidade pode levar a um comportamento inesperado.
Atenuações
Certifique-se de que o argumentsPropagateNullability
tem o mesmo número de elementos que o arguments
. Em caso de dúvida, use false
para o argumento de anulabilidade.
O método ToString()
agora retorna uma string vazia para instâncias null
Comportamento antigo
Anteriormente, o EF retornava resultados inconsistentes para o método ToString()
quando o valor do argumento era null
. Por exemplo, ToString()
na propriedade bool?
com o valor null
que retornou null
, mas para expressões de não-propriedade bool?
cujo valor foi null
, retornou True
. O comportamento também foi inconsistente para outros tipos de dados, por exemplo, ToString()
em null
valor enum retornou string vazia.
Novo comportamento
A partir do EF Core 9.0, o método ToString()
agora retorna consistentemente a cadeia de caracteres vazia em todos os casos em que o valor do argumento é null
.
Porquê
O comportamento antigo era inconsistente em diferentes tipos de dados e situações, além de não estar alinhado com o comportamento de C#.
Atenuações
Para reverter para o comportamento antigo, reescreva a consulta de acordo:
var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());
As dependências da estrutura compartilhada foram atualizadas para 9.0.x
Comportamento antigo
As aplicações que usam o SDK do Microsoft.NET.Sdk.Web
e estão dirigidas a net8.0 resolveriam pacotes como os System.Text.Json
, Microsoft.Extensions.Caching.Memory
, Microsoft.Extensions.Configuration.Abstractions
, Microsoft.Extensions.Logging
e Microsoft.Extensions.DependencyModel
da estrutura compartilhada, portanto, essas assemblagens normalmente não seriam implantadas com a aplicação.
Novo comportamento
Embora o EF Core 9.0 ainda suporte net8.0, ele agora faz referência às versões 9.0.x do System.Text.Json
, Microsoft.Extensions.Caching.Memory
, Microsoft.Extensions.Configuration.Abstractions
, Microsoft.Extensions.Logging
e Microsoft.Extensions.DependencyModel
. As aplicações destinadas ao net8.0 não poderão utilizar a estrutura partilhada para evitar a implantação destes conjuntos.
Porquê
As versões de dependência correspondentes contêm as correções de segurança mais recentes e usá-las simplifica o modelo de manutenção para o EF Core.
Atenuações
Altere seu aplicativo para o destino net9.0 para obter o comportamento anterior.
Mudanças disruptivas do Azure Cosmos DB
Foi feito um trabalho extensivo para tornar o provedor do Azure Cosmos DB melhor na versão 9.0. As mudanças incluem uma série de alterações de impacto significativo; Se estiver a atualizar uma aplicação existente, leia cuidadosamente o que se segue.
Alterações de alto impacto
A propriedade discriminator agora chama-se $type
em vez de Discriminator
Comportamento antigo
O EF adiciona automaticamente uma propriedade discriminadora aos documentos JSON para identificar o tipo de entidade que o documento representa. Em versões anteriores do EF, essa propriedade JSON costumava ser nomeada Discriminator
por padrão.
Novo comportamento
A partir do EF Core 9.0, a propriedade discriminator agora é chamada de $type
por padrão. Se você tiver documentos existentes no Azure Cosmos DB de versões anteriores do EF, eles usarão a nomenclatura Discriminator
antiga e, após a atualização para o EF 9.0, as consultas nesses documentos falharão.
Porquê
Uma prática JSON emergente usa uma propriedade $type
em cenários onde o tipo de um documento precisa ser identificado. Por exemplo, o System.Text.Json do .NET também suporta polimorfismo, usando $type
como seu nome de propriedade discriminador padrão (docs). Para alinhar com o resto do ecossistema e facilitar a interoperação com ferramentas externas, o padrão foi alterado.
Atenuações
A atenuação mais fácil é simplesmente configurar o nome da propriedade discriminadora para ser Discriminator
, assim como antes:
modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");
Fazer isso para todos os tipos de entidade de nível superior fará com que o EF se comporte exatamente como antes.
Neste ponto, se desejar, você também pode atualizar todos os seus documentos para usar a nova nomenclatura $type
.
A propriedade id
agora contém apenas a propriedade de chave EF por predefinição
Comportamento antigo
Anteriormente, o EF inseria o valor discriminador do seu tipo de entidade na propriedade id
do documento. Por exemplo, se você salvou um tipo de entidade Blog
com uma propriedade Id
contendo 8, a propriedade JSON id
conterá Blog|8
.
Novo comportamento
A partir do EF Core 9.0, a propriedade JSON id
não contém mais o valor do discriminador e contém apenas o valor da propriedade da chave. Para o exemplo acima, a propriedade JSON id
seria simplesmente 8
. Se você tiver documentos existentes no Azure Cosmos DB de versões anteriores do EF, eles terão o valor discriminador na propriedade JSON id
e, após a atualização para o EF 9.0, as consultas nesses documentos falharão.
Porquê
Como a propriedade JSON id
deve ser exclusiva, o discriminador foi adicionado anteriormente a ela para permitir que entidades diferentes com o mesmo valor de chave existam. Por exemplo, isso permitia ter um Blog
e um Post
com uma propriedade Id
contendo o valor 8 dentro do mesmo contêiner e partição. Isso se alinhou melhor com os padrões de modelagem de dados de banco de dados relacional, onde cada tipo de entidade é mapeado para sua própria tabela e, portanto, tem seu próprio espaço-chave.
O EF 9.0 geralmente alterou o mapeamento para ficar mais alinhado com as práticas e expectativas comuns do Azure Cosmos DB NoSQL, em vez de corresponder às expectativas dos usuários provenientes de bancos de dados relacionais. Além disso, ter o valor discriminador na propriedade id
tornou mais difícil para ferramentas e sistemas externos interagirem com documentos JSON gerados pelo EF; esses sistemas externos geralmente não estão cientes dos valores do discriminador EF, que são, por padrão, derivados de tipos .NET.
Atenuações
A atenuação mais fácil é simplesmente configurar o EF para incluir o discriminador na propriedade JSON id
, como antes. Uma nova opção de configuração foi introduzida para este fim:
modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();
Fazer isso para todos os seus tipos de entidade de nível superior fará com que o EF (Entity Framework) se comporte como antes.
Neste ponto, se desejar, você também pode atualizar todos os seus documentos para reescrever sua propriedade JSON id
. Observe que isso só é possível se entidades de tipos diferentes não compartilharem o mesmo valor de id dentro do mesmo contêiner.
Alterações de impacto médio
Não há mais suporte para a sincronização de E/S por meio do provedor do Azure Cosmos DB
Problema de rastreamento #32563
Comportamento antigo
Anteriormente, chamar métodos síncronos como ToList
ou SaveChanges
fazia com que o EF Core bloqueasse de forma síncrona usando .GetAwaiter().GetResult()
ao executar chamadas assíncronas no SDK do Azure Cosmos DB. Isso pode resultar em impasse.
Novo comportamento
A partir do EF Core 9.0, o EF agora lança por padrão ao tentar usar E/S síncronas. A mensagem de exceção é "O Azure Cosmos DB não oferece suporte a E/S síncrona. Certifique-se de usar e aguardar corretamente apenas métodos assíncronos ao usar o Entity Framework Core para acessar o Azure Cosmos DB. Consulte https://aka.ms/ef-cosmos-nosync para obter mais informações."
Porquê
O bloqueio síncrono em métodos assíncronos pode resultar em impasse, e o SDK do Azure Cosmos DB dá suporte apenas a métodos assíncronos.
Atenuações
No EF Core 9.0, o erro pode ser suprimido com:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}
Dito isso, os aplicativos devem parar de usar APIs de sincronização com o Azure Cosmos DB, pois isso não é suportado pelo SDK do Azure Cosmos DB. A capacidade de suprimir a exceção será removida em uma versão futura do EF Core, após a qual a única opção será usar APIs assíncronas.
As consultas SQL agora devem projetar valores JSON diretamente
Acompanhamento do Pedido #25527
Comportamento antigo
Anteriormente, o EF gerava consultas como as seguintes:
SELECT c["City"] FROM root c
Essas consultas fazem com que o Azure Cosmos DB envolva cada resultado em um objeto JSON, da seguinte maneira:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
Novo comportamento
A partir do EF Core 9.0, o EF agora adiciona o modificador VALUE
às consultas da seguinte maneira:
SELECT VALUE c["City"] FROM root c
Essas consultas fazem com que o Azure Cosmos DB retorne os valores diretamente, sem ser encapsulado:
[
"Berlin",
"México D.F."
]
Se seu aplicativo fizer uso de consultas SQL, essas consultas provavelmente serão interrompidas após a atualização para o EF 9.0, pois não incluem o modificador VALUE
.
Porquê
Envolver cada resultado em um objeto JSON adicional pode causar degradação de desempenho em alguns cenários, incha a carga útil do resultado JSON e não é a maneira natural de trabalhar com o Azure Cosmos DB.
Atenuações
Para atenuar, basta adicionar o modificador VALUE
às projeções de suas consultas SQL, conforme mostrado acima.
Os resultados indefinidos agora são filtrados automaticamente dos resultados da consulta
Comportamento antigo
Anteriormente, o EF gerava consultas como as seguintes:
SELECT c["City"] FROM root c
Essas consultas fazem com que o Azure Cosmos DB envolva cada resultado em um objeto JSON, da seguinte maneira:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
Se qualquer um dos resultados fosse indefinido (por exemplo, a propriedade City
estava ausente do documento), um documento vazio era retornado e o EF retornava null
para esse resultado.
Novo comportamento
A partir do EF Core 9.0, o EF agora adiciona o modificador VALUE
às consultas da seguinte maneira:
SELECT VALUE c["City"] FROM root c
Essas consultas fazem com que o Azure Cosmos DB retorne os valores diretamente, sem ser encapsulado:
[
"Berlin",
"México D.F."
]
O comportamento do Azure Cosmos DB é filtrar automaticamente undefined
valores dos resultados; Isso significa que, se uma das propriedades City
estiver ausente do documento, a consulta retornará apenas um único resultado, em vez de dois resultados, sendo um null
.
Porquê
Envolver cada resultado em um objeto JSON adicional pode causar degradação de desempenho em alguns cenários, incha a carga útil do resultado JSON e não é a maneira natural de trabalhar com o Azure Cosmos DB.
Atenuações
Se a obtenção de valores null
para resultados indefinidos for importante para seu aplicativo, agrupe os valores de undefined
para null
usando o novo operador EF.Functions.Coalesce
:
var users = await context.Customer
.Select(c => EF.Functions.CoalesceUndefined(c.City, null))
.ToListAsync();
As consultas traduzidas incorretamente não são mais traduzidas
Comportamento antigo
Anteriormente, o EF traduzia consultas como as seguintes:
var sessions = await context.Sessions
.Take(5)
.Where(s => s.Name.StartsWith("f"))
.ToListAsync();
No entanto, a tradução SQL para esta consulta estava incorreta:
SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0
Em SQL, a cláusula WHERE
é avaliada antes de as cláusulas OFFSET
e LIMIT
; mas na consulta LINQ acima, o operador Take
aparece antes do operador Where
. Como resultado, essas consultas podem retornar resultados incorretos.
Novo comportamento
A partir do EF Core 9.0, essas consultas não são mais traduzidas e uma exceção é lançada.
Porquê
Traduções incorretas podem causar corrupção silenciosa de dados, o que pode introduzir bugs difíceis de descobrir em seu aplicativo. O EF sempre prefere falhar rapidamente de forma proativa, agindo imediatamente, em vez de possivelmente causar corrupção de dados.
Atenuações
Se você estava satisfeito com o comportamento anterior e gostaria de executar o mesmo SQL, basta trocar a ordem dos operadores LINQ:
var sessions = await context.Sessions
.Where(s => s.Name.StartsWith("f"))
.Take(5)
.ToListAsync();
Infelizmente, o Azure Cosmos DB atualmente não oferece suporte às cláusulas OFFSET
e LIMIT
em subconsultas SQL, que é o que a tradução adequada da consulta LINQ original exige.
Alterações de baixo impacto
HasIndex
agora lança em vez de ser ignorado
Problema de rastreamento #34023
Comportamento antigo
Anteriormente, as chamadas para HasIndex eram ignoradas pelo provedor do EF Cosmos DB.
Novo comportamento
O provedor agora lança se HasIndex for especificado.
Porquê
No Azure Cosmos DB, todas as propriedades são indexadas por padrão e nenhuma indexação precisa ser especificada. Embora seja possível definir uma política de indexação personalizada, isso não é suportado atualmente pelo EF e pode ser feito por meio do Portal do Azure sem suporte ao EF. Como as chamadas HasIndex não estavam a fazer nada, deixaram de ser permitidas.
Atenuações
Remova todas as chamadas para HasIndex.
IncludeRootDiscriminatorInJsonId
foi renomeado para HasRootDiscriminatorInJsonId
após 9.0.0-rc.2
Problema de rastreamento #34717
Comportamento antigo
A API IncludeRootDiscriminatorInJsonId
foi introduzida na 9.0.0 rc.1.
Novo comportamento
Para a versão final do EF Core 9.0, a API foi renomeada para HasRootDiscriminatorInJsonId
Porquê
Outra API relacionada foi renomeada para começar com Has
em vez de Include
e, portanto, esta foi renomeada para consistência também.
Atenuações
Se o seu código estiver usando a API IncludeRootDiscriminatorInJsonId
, basta alterá-lo para fazer referência a HasRootDiscriminatorInJsonId
em vez disso.