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
Exceção é gerada ao aplicar migrações se houver alterações de modelo pendentes Alto
Exceção é gerada ao aplicar migrações em uma transação explícita Alto
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 alto impacto

A exceção será gerada ao aplicar migrações se houver alterações de modelo pendentes

Acompanhamento de problema nº 33732

Comportamento antigo

Se o modelo tiver alterações pendentes em comparação com a última migração, estas alterações não serão aplicadas juntamente com o restante das migrações quando Migrate for chamado.

Novo comportamento

A partir do EF Core 9.0, se o modelo tiver alterações pendentes em comparação com a última migração, uma exceção será gerada quando dotnet ef database update, Migrate ou MigrateAsync for chamado:

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'.

Por que

Esquecer de adicionar uma nova migração depois de fazer alterações de 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.

Mitigações

Há várias situações comuns quando essa exceção pode ser gerada:

  • Não há migrações. Isso é comum quando o banco de dados é atualizado por outros meios.
    • Mitigação: se você não planeja usar migrações para gerenciar o esquema de banco de dados, remova a chamada Migrate ou MigrateAsync, senão adicione uma migração.
  • Há pelo menos uma migração, mas o instantâneo do modelo está ausente. Isso é comum para migrações criadas manualmente.
    • Mitigação: Adicionar uma nova migração usando ferramentas do EF, isso atualizará o instantâneo do modelo.
  • O modelo não foi modificado pelo desenvolvedor, mas é criado de forma não determinística, fazendo com que o EF o detecte como modificado. Isso é comum quando new DateTime(), DateTime.Now, DateTime.UtcNowou Guid.NewGuid() são usados em objetos fornecidos para 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 depois que o modelo for corrigido. Se dados dinâmicos precisarem 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 do 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 nesse caso e deveria ser suprimido:

      options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))

Se o seu cenário não se enquadrar em nenhum dos casos acima e adicionar uma nova migração criar a mesma migração sempre ou uma migração vazia e a exceção ainda for gerada, crie um pequeno projeto de reprodução e compartilhe-a com a equipe do EF em um novo problema.

A exceção é gerada ao aplicar migrações em uma transação explícita

Acompanhamento de problema nº 17578

Comportamento antigo

Para aplicar migrações resilientemente, o seguinte padrão era comumente usado:

await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () =>
{
    await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
    await dbContext.Database.MigrateAsync(cancellationToken);
    await transaction.CommitAsync(cancellationToken);
});

Novo comportamento

A partir do EF Core 9.0, as chamadas Migrate e MigrateAsync iniciarão uma transação e executarão os comandos usando um ExecutionStrategy e, se o aplicativo usar o padrão acima, uma exceção será gerada:

Um erro foi gerado para o aviso 'Microsoft.EntityFrameworkCore.Migrations.MigrationsUserTransactionWarning': Uma transação foi iniciada antes da aplicação das migrações. Isso impede que um bloqueio de banco de dados seja adquirido e, portanto, o banco de dados não será protegido contra aplicativos de migração simultâneos. As transações e a estratégia de execução já são gerenciadas pelo EF, conforme necessário. Remova a transação externa. Essa exceção pode ser suprimida ou registrada passando a ID de evento 'RelationalEventId.MigrationsUserTransactionWarning' para o método 'ConfigureWarnings' em 'DbContext.OnConfiguring' ou 'AddDbContext'.

Por que

O uso de uma transação explícita impede que um bloqueio de banco de dados seja adquirido e, portanto, o banco de dados não será protegido contra aplicativos de migração simultâneos, limitando também o EF sobre como ele pode gerenciar as transações internamente.

Mitigações

Se houver apenas uma chamada de banco de dados dentro da transação, remova a transação externa e ExecutionStrategy:

await dbContext.Database.MigrateAsync(cancellationToken);

Caso contrário, se o cenário exigir uma transação explícita e você tiver outro mecanismo em vigor para impedir o aplicativo de migração simultâneo, ignore o aviso:

options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsUserTransactionWarning))

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.

Mitigaçõ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 bool? com valor bool? retornava null, mas para expressões bool? que não são de propriedade cujo valor era null, retornava True. 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 propriedade JSON id é mapeada para a chave 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.

A propriedade JSON id é mapeada para a chave

Acompanhamento de problema nº 34179

Comportamento antigo

Anteriormente, o EF criava uma propriedade de sombra mapeada para a propriedade JSON id, a menos que alguma das propriedades fosse mapeada explicitamente para id.

Novo comportamento

A partir do EF Core 9, a propriedade de chave será mapeada por convenção para a propriedade JSON id, se possível. Isso significa que a propriedade chave não será mais mantida no documento em um nome diferente com o mesmo valor, portanto, o código não EF que consome os documentos e depende dessa propriedade que está sendo presente não funcionará mais corretamente.

Por que

O EF 9.0 geralmente alterou o mapeamento para estar mais alinhado com as práticas e expectativas comuns do NoSQL do Azure Cosmos DB. E não é comum armazenar o valor da chave duas vezes no documento.

Mitigações

Se você quiser preservar o comportamento do EF Core 8, a mitigação mais fácil será usar uma nova opção de configuração que foi introduzida para esta finalidade:

modelBuilder.Entity<Session>().HasShadowId();

Ao fazer isso para todos os tipos de entidade de nível superior, o EF se comportará exatamente como antes. Ou você pode aplicá-lo a todos os tipos de entidade no modelo com uma chamada:

modelBuilder.HasShadowIds();

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.

Mitigaçõ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.