Alterações interruptivas no Entity Framework Core 9 (EF9)
Esta página documenta a API e as alterações de comportamento que têm o potencial de interromper a atualização de aplicativos existentes do Entity Framework Core 8 para o Entity Framework Core 9. Certifique-se de verificar as alterações interruptivas anteriores se estiver atualizando a partir de uma versão anterior do Entity Framework Core:
- Alterações interruptivas no EF Core 8
- Alterações interruptivas no EF Core 7
- Alterações interruptivas no EF Core 6
Estrutura de Destino
O Entity Framework 9 tem como destino o .NET 8. Isso significa que os aplicativos existentes que têm como destino o .NET 8 podem continuar a fazê-lo. Os aplicativos que têm como alvo versões mais antigas do .NET, .NET Core e .NET Framework precisarão ter como destino o .NET 8 ou o .NET 9 para usar o Entity Framework Core 9.
Resumo
Observação
Se você estiver usando o Azure Cosmos DB, consulte a seção separada abaixo sobre alterações interruptivas do Azure Cosmos DB.
Alterações de baixo impacto
EF.Functions.Unhex()
agora retorna byte[]?
Acompanhamento do problema #33864
Comportamento antigo
A função EF.Functions.Unhex()
foi anotada anteriormente para retornar byte[]
.
Novo comportamento
A partir do EF Core 9.0, Unhex() agora é anotada para retornar byte[]?
.
Por que
Unhex()
é traduzido para a função SQLite unhex
, que retorna NULL para entradas inválidas. Como resultado, Unhex()
retornou null
para esses casos, violando a anotação.
Mitigações
Se você tiver certeza se o conteúdo de texto passado Unhex()
representa uma cadeia de caracteres hexadecimal válida, basta adicionar o operador tolerante a nulo como uma declaração de que a invocação nunca retornará nulo:
var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();
Caso contrário, adicione verificações de runtime para nulo no valor de retorno de Unhex().
Aridade dos argumentos de nulidade da SqlFunctionExpression validada
Acompanhamento do problema #33852
Comportamento antigo
Anteriormente, era possível criar um SqlFunctionExpression
com um número diferente de argumentos e argumentos de propagação de nulidade.
Novo comportamento
A partir do EF Core 9.0, o EF agora lança uma exceção se o número de argumentos e os argumentos de propagação de nulidade não corresponderem.
Por que
O fato de não haver correspondência entre o número de argumentos e os argumentos de propagação de nulidade pode levar a um comportamento inesperado.
Atenuações
Certifique-se de que o argumentsPropagateNullability
tenha o mesmo número de elementos que o arguments
. Em caso de dúvida, use false
como argumento de nulabilidade.
ToString()
agora retorna uma string vazia para instâncias de null
Acompanhamento de problema nº 33941
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 null
com valor null
retornava null
, mas para expressões bool?
que não são de propriedade cujo valor era null
, retornava bool?
. O comportamento também era inconsistente para outros tipos de dados, por exemplo, ToString()
na enumeração de valor null
retornava uma string vazia.
Novo comportamento
A partir do EF Core 9.0, o método ToString()
agora retorna consistentemente uma string vazia em todos os casos em que o valor do argumento é null
.
Por que
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#.
Mitigações
Para reverter para o comportamento antigo, reescreva a consulta conforme necessário:
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 de estrutura compartilhada foram atualizadas para 9.0.x
Comportamento antigo
Os aplicativos que usam o SDK Microsoft.NET.Sdk.Web
e direcionam para o net8.0 resolveriam pacotes como System.Text.Json
, Microsoft.Extensions.Caching.Memory
, Microsoft.Extensions.Configuration.Abstractions
, Microsoft.Extensions.Logging
e Microsoft.Extensions.DependencyModel
na estrutura compartilhada. Desse modo, esses assemblies normalmente não seriam implantados com o aplicativo.
Novo comportamento
Embora o EF Core 9.0 ainda dê suporte ao net8.0, ele agora faz referência às versões 9.0.x de System.Text.Json
, Microsoft.Extensions.Caching.Memory
, Microsoft.Extensions.Configuration.Abstractions
, Microsoft.Extensions.Logging
e Microsoft.Extensions.DependencyModel
. Os aplicativos que direcionam para o net8.0 não poderão aproveitar a estrutura compartilhada para evitar a implantação desses assemblies.
Por que
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 do EF Core.
Mitigações
Altere seu aplicativo para direcionar ao net9.0 para obter o comportamento anterior.
Alterações interruptivas do Azure Cosmos DB
Um trabalho extensivo foi feito para aprimorar o provedor do Azure Cosmos DB na versão 9.0. As alterações incluem uma série de alterações significativas de alto impacto; se você estiver atualizando um aplicativo existente, leia o seguinte com atenção.
Alterações de alto impacto
A propriedade discriminatória agora é chamada de $type
em vez de Discriminator
Acompanhamento de problema nº 34269
Comportamento antigo
O EF adiciona automaticamente uma propriedade discriminatória a documentos JSON para identificar o tipo de entidade que o documento representa. Nas 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 discriminatória 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 antiga Discriminator
e, após a atualização para o EF 9.0, as consultas com esses documentos falharão.
Por que
Uma prática JSON emergente usa uma propriedade $type
em cenários em que o tipo de um documento precisa ser identificado. Por exemplo, o System.Text.Json do .NET também oferece suporte ao polimorfismo, usando $type
como seu nome de propriedade discriminatória padrão (documentos). Para se alinhar com o restante do ecossistema e facilitar a interoperação com ferramentas externas, o padrão foi alterado.
Mitigações
A forma mais fácil de mitigar é simplesmente configurar o nome da propriedade discriminatória como Discriminator
, assim como antes:
modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");
Ao fazer isso para todos os tipos de entidade de nível superior, o EF se comportará exatamente como antes.
Neste ponto, se preferir, você também pode atualizar todos os seus documentos para usar a nova $type
nomenclatura.
A propriedade id
agora contém apenas a propriedade de chave EF por padrão
Acompanhamento de problema nº 34179
Comportamento antigo
Antes, o EF inseria o valor discriminatório do tipo de entidade na propriedade id
do documento. Por exemplo, se você tiver salvo um tipo de entidade Blog
com uma propriedade Id
que contém 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 discriminatório e contém apenas o valor da propriedade de 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 discriminatório na propriedade JSON id
e, após a atualização para o EF 9.0, as consultas com esses documentos falharão.
Por que
Como a propriedade JSON id
deve ser exclusiva, o discriminador era adicionado anteriormente para permitir a existência de entidades diferentes com o mesmo valor de chave. Por exemplo, isso permitiu ter Blog
e 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, em que cada tipo de entidade é mapeado para sua própria tabela e, portanto, tem seu próprio espaço de chaves.
O EF 9.0 geralmente alterava o mapeamento para ficar mais alinhado com as práticas e expectativas NoSQL comuns do Azure Cosmos DB, em vez de corresponder às expectativas dos usuários que vêm de bancos de dados relacionais. Além disso, a presença do 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 são cientes dos valores discriminatórios do EF, que são derivados de tipos .NET por padrão.
Mitigações
A forma mais fácil de mitigar é simplesmente configurar o EF para incluir o discriminador na propriedade JSON id
, assim como antes. Uma nova opção de configuração foi introduzida para essa finalidade:
modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();
Ao fazer isso para todos os tipos de entidade de nível superior, o EF se comportará exatamente como antes.
Neste ponto, se preferir, você também pode atualizar todos os seus documentos para reescrever sua propriedade JSON id
. Observe que isso só será possível se entidades de tipos diferentes não compartilharem o mesmo valor de id no mesmo contêiner.
Alterações de impacto médio
A sincronização de E/S por meio do provedor Azure Cosmos DB não tem mais suporte
Rastreamento do problema #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 contra o SDK do Azure Cosmos DB. Isso pode resultar em deadlock.
Novo comportamento
A partir do EF Core 9.0, o EF agora gera uma exceção por padrão ao tentar usar a E/S síncrona. A mensagem de exceção é "O Azure Cosmos DB não tem suporte para 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."
Por que
O bloqueio síncrono em métodos assíncronos pode resultar em deadlock, e o SDK do Azure Cosmos DB só dá suporte para 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 não há suporte para isso no 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 de problema nº 25527
Comportamento antigo
Antes, 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 forma:
[
{
"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 forma:
SELECT VALUE c["City"] FROM root c
Essas consultas fazem com que o Azure Cosmos DB retorne os valores diretamente, sem serem envolvidos:
[
"Berlin",
"México D.F."
]
Se o aplicativo usar 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
.
Por que
Envolver cada resultado em um objeto JSON adicional pode causar degradação do desempenho em alguns cenários, inchar a carga do resultado JSON e não é a maneira natural de trabalhar com o Azure Cosmos DB.
Mitigações
Para mitigar, basta adicionar o modificador VALUE
às projeções de suas consultas SQL, conforme mostrado acima.
Os resultados indefinidos agora são filtrados automaticamente a partir dos resultados da consulta
Acompanhamento de problema nº 25527
Comportamento antigo
Antes, 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 forma:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
Se algum dos resultados fosse indefinido (por exemplo, a propriedade City
estava ausente no 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 forma:
SELECT VALUE c["City"] FROM root c
Essas consultas fazem com que o Azure Cosmos DB retorne os valores diretamente, sem serem envolvidos:
[
"Berlin",
"México D.F."
]
O comportamento do Azure Cosmos DB é filtrar undefined
os valores dos resultados automaticamente; isso significa que, se uma das propriedades City
estiver ausente no documento, a consulta retornará um único resultado, em vez de dois resultados, sendo um deles null
.
Por que
Envolver cada resultado em um objeto JSON adicional pode causar degradação do desempenho em alguns cenários, inchar a carga do resultado JSON e não é a maneira natural de trabalhar com o Azure Cosmos DB.
Mitigações
Se a obtenção null
de valores para resultados indefinidos for importante para o seu aplicativo, una os valores undefined
para null
usando o novo EF.Functions.Coalesce
operador:
var users = await context.Customer
.Select(c => EF.Functions.CoalesceUndefined(c.City, null))
.ToListAsync();
As consultas traduzidas incorretamente não são mais traduzidas
Acompanhamento de problema nº 34123
Comportamento antigo
Antes, 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
No SQL, a cláusula WHERE
é avaliada antes das cláusulas OFFSET
e LIMIT
; mas na consulta LINQ acima, o operador Take
aparece antes do operador Where
. Isso pode fazer com que essas consultas retornem resultados incorretos.
Novo comportamento
A partir do EF Core 9.0, essas consultas não são mais traduzidas e uma exceção é gerada.
Por que
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 mediante um lançamento em vez de possivelmente causar corrupção de dados.
Mitigaçõ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 não dá suporte às cláusulas OFFSET
e LIMIT
em subconsultas SQL no momento, que é o que a tradução adequada da consulta LINQ original exige.
Alterações de baixo impacto
HasIndex
agora é lançado em vez de ignorado
Acompanhamento de problema nº 34023
Comportamento antigo
Anteriormente, as chamadas para HasIndex eram ignoradas pelo provedor EF do Cosmos DB.
Novo comportamento
O provedor agora é lançado se HasIndex for especificado.
Por que
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 tem suporte do EF no momento e pode ser feito por meio do Portal do Azure sem suporte do EF. Como as chamadas HasIndex não estavam fazendo nada, elas não são mais permitidas.
Mitigações
Remova quaisquer chamadas para HasIndex.
IncludeRootDiscriminatorInJsonId
foi renomeado para HasRootDiscriminatorInJsonId
após o 9.0.0-rc.2
Acompanhamento de problema nº 34717
Comportamento antigo
A API IncludeRootDiscriminatorInJsonId
foi introduzida na versão 9.0.0 rc.1.
Novo comportamento
Para a versão final do EF Core 9.0, a API foi renomeada para HasRootDiscriminatorInJsonId
Por que
Outra API relacionada foi renomeada para começar em Has
vez de Include
, e esta também foi renomeada para consistência.
Mitigações
Se o código estiver usando a API IncludeRootDiscriminatorInJsonId
, basta alterá-lo para referenciar HasRootDiscriminatorInJsonId
.