Compartilhar via


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:

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 da falha Impacto
EF.Functions.Unhex() agora retorna byte[]? Baixo
A aridade dos argumentos de nulabilidade do SqlFunctionExpression foi validada Baixo
ToString() agora retorna uma string vazia para instâncias de null Baixo
As dependências de estrutura compartilhada foram atualizadas para 9.0.x Baixo

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 da falha Impacto
A propriedade discriminatória agora é chamada de $type em vez de Discriminator Alto
A propriedade id não contém mais o discriminador por padrão Alto
A sincronização de E/S por meio do provedor Azure Cosmos DB não tem mais suporte Médio
As consultas SQL agora devem projetar valores JSON diretamente Médio
Os resultados indefinidos agora são filtrados automaticamente a partir dos resultados da consulta Médio
As consultas traduzidas incorretamente não são mais traduzidas Médio
HasIndex agora é lançado em vez de ignorado Baixo
IncludeRootDiscriminatorInJsonId foi renomeado para HasRootDiscriminatorInJsonId após o 9.0.0-rc.2 Baixo

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.