Partilhar via


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.

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.

Mudança disruptiva Impacto
exceção é lançada ao aplicar migrações se houver alterações de modelo pendentes Alto
EF.Functions.Unhex() agora retorna byte[]? Baixo
A aridade dos argumentos de nulidade de SqlFunctionExpression foi validada Baixo
ToString() método agora retorna cadeia de caracteres vazia para null instâncias Baixo
As dependências da estrutura compartilhada foram atualizadas para 9.0.x Baixo

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 ou MigrateAsync, caso contrário, adicione uma migração.
  • 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.UtcNowou Guid.NewGuid() são usados em objetos fornecidos a 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 HasData().
  • A última migração foi criada para um provedor diferente daquele usado para aplicar as migrações.
  • 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[]?

Tracking Issue #33864

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

Tracking Issue #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 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.

Quebrando a mudança Impacto
A propriedade discriminador agora é chamada $type em vez de Discriminator Alto
A propriedade id não contém mais o discriminador por padrão Alto
Entrada/Saída de sincronização através do provedor do Azure Cosmos DB já não é suportada Média
consultas SQL agora devem projetar valores JSON diretamente Médio
Os resultados indefinidos agora são filtrados automaticamente dos resultados da consulta Médio
As consultas traduzidas incorretamente não são mais traduzidas Média
HasIndex agora lança em vez de ser ignorado Baixo
IncludeRootDiscriminatorInJsonId foi renomeado para HasRootDiscriminatorInJsonId após 9.0.0-rc.2 Baixo

Alterações de alto impacto

A propriedade discriminator agora chama-se $type em vez de Discriminator

Tracking Issue #34269

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

Tracking Issue #34179

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

Tracking Issue #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."
    }
]

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

Tracking Issue #34123

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 Includee, 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.