Partilhar via


Alterações interruptivas no EF Core 6.0

As seguintes alterações de API e comportamento têm o potencial de interromper aplicativos existentes ao atualizar para o EF Core 6.0.

Estrutura de Destino

O EF Core 6.0 destina-se ao .NET 6. Aplicativos destinados a versões anteriores do .NET, .NET Core e .NET Framework precisarão destinar-se ao .NET 6 para usarem o EF Core 6.0.

Resumo

Alterações da falha Impacto
Dependentes opcionais aninhados que compartilham uma tabela e sem propriedades obrigatórias não podem ser salvos Alto
Alterar o proprietário de uma entidade de propriedade agora gera uma exceção Médio
Azure Cosmos DB: os tipos de entidade relacionados são descobertos como com proprietário Médio
SQLite: as conexões são agrupadas em pool Médio
As relações muitos para muitos sem entidades de junção mapeadas agora são com scaffold Médio
O mapeamento entre DeleteBehavior e valores ON DELETE foi limpo Baixo
O banco de dados na memória valida que as propriedades necessárias não contêm valores nulos Baixo
O último ORDER BY é removido ao ingressar em coleções Baixo
O DbSet não implementa mais IAsyncEnumerable Baixo
O tipo de entidade de retorno TVF também é mapeado para uma tabela por padrão Baixo
A exclusividade do nome de restrição de verificação agora é validada Baixo
Interfaces de metadados IReadOnly foram adicionadas e métodos de extensão foram removidos Baixo
IExecutionStrategy agora é um serviço singleton Baixo
SQL Server: mais erros são considerados transitórios Baixo
Azure Cosmos DB: mais caracteres são escapados em valores de 'id' Baixo
Alguns serviços singleton agora estão no escopo Baixo*
Nova API de cache para extensões que adicionam ou substituem serviços Baixo*
Novo procedimento de inicialização de modelo em tempo de design e instantâneo Baixo
OwnedNavigationBuilder.HasIndex agora retorna um tipo diferente Baixo
DbFunctionBuilder.HasSchema(null) substitui [DbFunction(Schema = "schema")] Baixo
Navegações pré-configuradas são substituídas por valores de consultas de banco de dados Baixo
Valores de cadeia de caracteres de enumeração desconhecidos no banco de dados não são convertidos para o padrão de enumeração quando consultados Baixo
DbFunctionBuilder.HasTranslation agora fornece os argumentos da função como IReadOnlyList em vez de IReadOnlyCollection Baixo
O mapeamento de tabela padrão não é removido quando a entidade é mapeada para uma função com valor de tabela Baixo
O dotnet-ef destina-se ao .NET 6 Baixo
As implementações de IModelCacheKeyFactory talvez precisem ser atualizadas para lidarem com cache em tempo de design Baixo
NavigationBaseIncludeIgnored agora é um erro por padrão Baixo

* Essas alterações são de interesse particular para autores de provedores de banco de dados e extensões.

Alterações de alto impacto

Dependentes opcionais aninhados que compartilham uma tabela e sem propriedades obrigatórias não são permitidos

Problema de acompanhamento n. 24558

Comportamento antigo

Modelos com dependentes opcionais aninhados que compartilham uma tabela e sem propriedades obrigatórias foram permitidos, mas podem resultar em perda de dados ao consultar os dados e depois salvar novamente. Por exemplo, considere o seguinte modelo:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Nenhuma das propriedades em ContactInfo ou Address são necessárias, e todos esses tipos de entidade são mapeados para a mesma tabela. As regras para dependentes opcionais (em vez de dependentes necessários) dizem que, se todas as colunas de ContactInfo forem nulas, nenhuma instância do ContactInfo será criada ao consultar o proprietário Customer. No entanto, isso também significa que nenhuma instância do Address será criada, mesmo se as colunas Address não forem nulas.

Novo comportamento

A tentativa de usar esse modelo gerará agora a seguinte exceção:

System.InvalidOperationException: o tipo de entidade 'ContactInfo' é um dependente opcional usando o compartilhamento de tabela e contém outros dependentes sem nenhuma propriedade não compartilhada necessária para identificar se a entidade existe. Se todas as propriedades anuláveis contiverem um valor nulo no banco de dados, uma instância de objeto não será criada na consulta, causando a perda dos valores dos dependentes aninhados. Adicione uma propriedade necessária para criar instâncias com valores nulos para outras propriedades ou marque a navegação de entrada como necessária para sempre criar uma instância.

Isso impede a perda de dados ao consultar e salvar dados.

Por que

O uso de modelos com dependentes opcionais aninhados que compartilham uma tabela e sem propriedades obrigatórias muitas vezes resultou em perda de dados silenciosa.

Atenuações

Evite usar dependentes opcionais compartilhando uma tabela e sem propriedades necessárias. Há três maneiras fáceis de fazer isso:

  1. Torne os dependentes necessários. Isso significa que a entidade dependente sempre terá um valor depois de ser consultada, mesmo que todas as propriedades dela sejam nulas. Por exemplo:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

    Ou:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. Verifique se o dependente contém pelo menos uma propriedade necessária.

  3. Mapeie dependentes opcionais para a tabela deles, em vez de compartilhar uma tabela com a entidade de segurança. Por exemplo:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

Os problemas com dependentes opcionais e exemplos dessas atenuações estão incluídos na documentação para O que há de novo no EF Core 6.0.

Alterações de impacto médio

Alterar o proprietário de uma entidade de propriedade agora gera uma exceção

Problema de acompanhamento n. 4073

Comportamento antigo

Era possível reatribuir uma entidade de propriedade a uma entidade de proprietário diferente.

Novo comportamento

Essa ação agora lançará uma exceção:

A propriedade '{entityType}.{Property}' faz parte de uma chave e, portanto, não pode ser modificada nem marcada como modificada. Para alterar a entidade de segurança de uma entidade existente com uma chave estrangeira de identificação, primeiro exclua o dependente, invoque 'SaveChanges' e associe o dependente à nova entidade de segurança.

Por que

Embora não exijamos que as propriedades de chave existam em um tipo com proprietário, o EF ainda criará propriedades de sombra a serem usadas como a chave primária e a chave estrangeira apontando para o proprietário. Quando a entidade do proprietário é alterada, isso faz com que os valores da chave estrangeira na entidade de propriedade sejam alterados e, já que eles também são usados como a chave primária, isso resulta na alteração da identidade da entidade. Isso ainda não tem suporte total no EF Core e só era condicionalmente permitido para entidades com proprietário, às vezes fazendo com que o estado interno se tornasse inconsistente.

Atenuações

Em vez de atribuir a mesma instância com proprietário a um novo proprietário, você pode atribuir uma cópia e excluir a antiga.

Problema de acompanhamento n. 24803O que há de novo: padrão para propriedade implícita

Comportamento antigo

Como em outros provedores, os tipos de entidade relacionados foram descobertos como tipos normais (sem proprietário).

Novo comportamento

Os tipos de entidade relacionados agora serão de propriedade do tipo de entidade no qual foram descobertos. Somente os tipos de entidade que correspondem a uma propriedade DbSet<TEntity> serão descobertos como sem proprietário.

Por que

Esse comportamento segue o padrão comum de modelagem de dados no Azure Cosmos DB, de inserir dados relacionados em apenas um documento. O Azure Cosmos DB não dá suporte nativo à junção de documentos diferentes, portanto, a modelagem de entidades relacionadas como sem proprietário tem utilidade limitada.

Atenuações

Para configurar um tipo de entidade como sem proprietário, chame modelBuilder.Entity<MyEntity>();

SQLite: as conexões são agrupadas em pool

Problema de acompanhamento n. 13837O que há de novo: padrão para propriedade implícita

Comportamento antigo

Anteriormente, as conexões em Microsoft.Data.Sqlite não eram agrupadas em pool.

Novo comportamento

Da versão 6.0 em diante, as conexões agora são agrupadas em pool por padrão. Isso resulta em arquivos de banco de dados sendo mantidos abertos pelo processo mesmo depois que o objeto de conexão ADO.NET é fechado.

Por que

O pool de conexões subjacentes aprimora muito o desempenho da abertura e do fechamento de objetos de conexão ADO.NET. Isso é especialmente perceptível para cenários em que a abertura da conexão subjacente é cara, como no caso de criptografia, ou em cenários em que há muitas conexões de curta duração ao banco de dados.

Atenuações

O pool de conexões pode ser desabilitado adicionando Pooling=False a uma cadeia de conexão.

Alguns cenários (como excluir o arquivo de banco de dados) podem agora encontrar erros que informam que o arquivo ainda está em uso. Você pode limpar manualmente o pool de conexões antes de executar as operações do arquivo usando SqliteConnection.ClearPool().

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

As relações muitos para muitos sem entidades de junção mapeadas agora são com scaffold

Problema de acompanhamento n. 22475

Comportamento antigo

Scaffolding (engenharia reversa) de um DbContext e tipos de entidade de um banco de dados existente sempre mapearam explicitamente tabelas de junção para tipos de entidade de junção para relações muitos para muitos.

Novo comportamento

Tabelas de junção simples contendo apenas duas propriedades de chave estrangeira para outras tabelas não são mais mapeadas para tipos de entidade explícitos; em vez disso, são mapeadas como uma relação muitos para muitos entre as duas tabelas unidas.

Por que

Relações muitos para muitos sem tipos de junção explícitos foram introduzidas no EF Core 5.0 e são uma maneira mais limpa e natural de representar tabelas de junção simples.

Atenuações

Há duas atenuações. A abordagem preferencial é atualizar o código para usar diretamente as relações muitos para muitos. É muito raro que o tipo de entidade de junção precise ser usado diretamente quando contiver apenas duas chaves estrangeiras para as relações muitos para muitos.

Como alternativa, a entidade de junção explícita pode ser adicionada novamente ao modelo do EF. Por exemplo, pressupondo uma relação muitos-para-muitos entre Post e Tag, adicione de volta o tipo de junção e as navegações usando classes parciais:

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

Em seguida, adicione a configuração para o tipo de junção e as navegações a uma classe parcial para o DbContext:

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

Por fim, remova a configuração gerada para a relação muitos para muitos do contexto com scaffold. Isso é necessário porque o tipo de entidade de junção com scaffold precisa ser removido do modelo antes que o tipo explícito possa ser usado. Esse código precisará ser removido toda vez que o contexto for com scaffold, mas como o código acima está em classes parciais, ele persistirá.

Observe que, com essa configuração, a entidade de junção pode ser usada explicitamente, assim como nas versões anteriores do EF Core. No entanto, a relação também pode ser usada como uma relação muitos-para-muitos. Isso significa que a atualização de um código como esse pode ser uma solução temporária enquanto o restante do código é atualizado para usar a relação como muitos-para-muitos da forma natural.

Alterações de baixo impacto

O mapeamento entre DeleteBehavior e valores ON DELETE foi limpo

Problema de acompanhamento n. 21252

Comportamento antigo

Alguns dos mapeamentos entre o comportamento OnDelete() de uma relação e o comportamento ON DELETE das chaves estrangeiras no banco de dados eram inconsistentes em Migrações e Scaffolding.

Novo comportamento

A tabela a seguir ilustra as alterações para Migrações.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
Restringir RESTRICT
Cascade CASCADE
ClientCascade RESTRINGIRNENHUMA AÇÃO
SetNull SET NULL
ClientSetNull RESTRINGIRNENHUMA AÇÃO

As alterações para Scaffolding são conforme mostrado a seguir.

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNull Restringir
CASCADE Cascata
SET NULL SetNull

Por que

Os novos mapeamentos são mais consistentes. O comportamento de banco de dados padrão de NO ACTION agora é preferencial sobre o comportamento de RESTRICT, mais restritivo e menos funcional.

Atenuações

O comportamento padrão de OnDelete () de relações opcionais é ClientSetNull. Seu mapeamento foi alterado de RESTRICT para NO ACTION. Isso pode fazer com que muitas operações sejam geradas em sua primeira migração adicionada após a atualização para o EF Core 6.0.

Você pode optar por aplicar essas operações ou removê-las manualmente da migração, pois elas não têm impacto funcional no EF Core.

O SQL Server não dá suporte a RESTRICT, portanto, essas chaves estrangeiras já foram criadas usando NO ACTION. As operações de migração não terão nenhum efeito no SQL Server e poderão ser removidas com segurança.

O banco de dados na memória valida que as propriedades necessárias não contêm valores nulos

Problema de acompanhamento n. 10613

Comportamento antigo

O banco de dados na memória permitia salvar valores nulos mesmo quando a propriedade era configurada como necessária.

Novo comportamento

O banco de dados na memória gera um Microsoft.EntityFrameworkCore.DbUpdateException quando SaveChanges ou SaveChangesAsync é chamado e uma propriedade obrigatória é definida como null.

Por que

Agora, o comportamento do banco de dados na memória corresponde ao comportamento de outros bancos de dados.

Atenuações

O comportamento anterior (ou seja, não verificar valores nulos) pode ser restaurado ao configurar o provedor na memória. Por exemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

O último ORDER BY é removido ao ingressar em coleções

Problema de acompanhamento n. 19828

Comportamento antigo

Ao executar junções SQL em coleções (relações um-para-muitos), o EF Core costumava adicionar um ORDER BY para cada coluna de chave da tabela ingressada. Por exemplo, o carregamento de todos os blogs com as postagens relacionadas foi feito por meio do seguinte SQL:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

Essas ordenações são necessárias para a materialização adequada das entidades.

Novo comportamento

O último ORDER BY para uma junção de coleção agora é omitido:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

Um ORDER BY não é mais gerado para a coluna ID da postagem.

Por que

Cada ORDER BY impõe trabalho adicional no lado do banco de dados, e a última ordenação não é necessária para as necessidades de materialização do EF Core. Os dados mostram que a remoção dessa última ordenação pode produzir um aprimoramento significativo no desempenho em alguns cenários.

Atenuações

Se o aplicativo espera que entidades unidas sejam retornadas em uma ordem específica, faça isso explicitamente adicionando um operador LINQ OrderBy à sua consulta.

O DbSet não implementa mais IAsyncEnumerable

Problema de acompanhamento n. 24041

Comportamento antigo

DbSet<TEntity>, que é usado para executar consultas no DbContext, usado para implementar IAsyncEnumerable<T>.

Novo comportamento

DbSet<TEntity> não implementa mais IAsyncEnumerable<T> diretamente.

Por que

DbSet<TEntity> foi originalmente feito para implementar IAsyncEnumerable<T>, principalmente para permitir a enumeração direta nele por meio do constructo foreach. Infelizmente, quando um projeto também referencia System.Linq.Async para compor operadores LINQ assíncronos no lado do cliente, isso resultava em um erro de invocação ambíguo entre os operadores definidos por IQueryable<T> e aqueles definidos por IAsyncEnumerable<T>. O C# 9 adicionou suporte à extensão GetEnumerator para loops foreach, removendo o motivo principal original para fazer referência a IAsyncEnumerable.

A grande maioria dos usos de DbSet continuará a funcionar no estado em que se encontram, pois compõem operadores LINQ em DbSet, enumeram-no etc. Os únicos usos desfeitos são aqueles que tentam converter DbSet diretamente em IAsyncEnumerable.

Atenuações

Se você precisar se referir a um DbSet<TEntity> como um IAsyncEnumerable<T>, chame DbSet<TEntity>.AsAsyncEnumerable para convertê-lo explicitamente.

O tipo de entidade de retorno TVF também é mapeado para uma tabela por padrão

Problema de acompanhamento n. 23408

Comportamento antigo

Um tipo de entidade não foi mapeado para uma tabela por padrão quando usado como um tipo de retorno de um TVF configurado com HasDbFunction.

Novo comportamento

Um tipo de entidade usado como um tipo de retorno de um TVF retém o mapeamento de tabela padrão.

Por que

Não é intuitivo que a configuração de um TVF remova o mapeamento de tabela padrão para o tipo de entidade de retorno.

Atenuações

Para remover o mapeamento de tabela padrão, chame ToTable(EntityTypeBuilder, String):

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

A exclusividade do nome de restrição de verificação agora é validada

Problema de acompanhamento n.25061

Comportamento antigo

Restrições de verificação com o mesmo nome tinham permissão para serem declaradas e usadas na mesma tabela.

Novo comportamento

Configurar explicitamente duas restrições de verificação com o mesmo nome na mesma tabela agora resultará em uma exceção. Restrições de verificação criadas por uma convenção receberão um nome exclusivo.

Por que

A maioria dos bancos de dados não permite que duas restrições de verificação com o mesmo nome sejam criadas na mesma tabela, e alguns exigem que elas sejam exclusivas, mesmo entre tabelas. Isso resultaria no lançamento de uma exceção ao aplicar uma migração.

Atenuações

Em alguns casos, os nomes de restrição de verificação válidos podem ser diferentes devido a essa alteração. Para especificar explicitamente o nome desejado, chame HasName:

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

Interfaces de metadados IReadOnly foram adicionadas e métodos de extensão foram removidos

Problema de acompanhamento n.19213

Comportamento antigo

Havia três conjuntos de interfaces de metadados: IModel, IMutableModel e IConventionModel, além métodos de extensão.

Novo comportamento

Um novo conjunto de interfaces IReadOnly foi adicionado ao sistema, por exemplo, IReadOnlyModel. Métodos de extensão que foram definidos anteriormente para as interfaces de metadados foram convertidos em métodos de interface padrão.

Por que

Os métodos de interface padrão permitem que a implementação seja substituída, e a nova implementação de modelo de tempo de execução tira proveito disso para oferecer melhor desempenho.

Atenuações

Essas alterações não devem afetar a maioria dos códigos. No entanto, se você estava usando os métodos de extensão por meio da sintaxe de invocação estática, ela precisaria ser convertida em sintaxe de invocação de instância.

IExecutionStrategy agora é um serviço singleton

Problema de acompanhamento n.21350

Novo comportamento

IExecutionStrategy agora é um serviço singleton. Isso significa que qualquer estado adicionado em implementações personalizadas permanecerá entre as execuções, e o delegado passado para ExecutionStrategy só será executado uma vez.

Por que

Isso reduz as alocações em dois caminhos críticos no EF.

Atenuações

As implementações derivadas de ExecutionStrategy devem limpar qualquer estado em OnFirstExecution().

A lógica condicional no delegado passado para ExecutionStrategy deve ser movida para uma implementação personalizada de IExecutionStrategy.

SQL Server: mais erros são considerados transitórios

Problema de acompanhamento n.25050

Novo comportamento

Os erros listados no problema acima agora são considerados transitórios. Ao usar a estratégia de execução padrão (não tentar novamente), esses erros agora serão encapsulados em uma instância de exceção de adição.

Por que

Continuamos coletando comentários de usuários e da equipe do SQL Server sobre quais erros devem ser considerados transitórios.

Atenuações

Para alterar o conjunto de erros que são considerados transitórios, use uma estratégia de execução personalizada que pode ser derivada de SqlServerRetryingExecutionStrategy - Resiliência de Conexão – EF Core.

Azure Cosmos DB: mais caracteres são escapados em valores de 'id'

Problema de acompanhamento n.25100

Comportamento antigo

No EF Core 5, apenas '|' era escapado em valores id.

Novo comportamento

No EF Core 6, '/', '\', '?' e '#' também são escapados em valores id.

Por que

Esses caracteres são inválidos, conforme documentado em Resource.Id. Usá-los em id fará com que as consultas falhem.

Atenuações

Você pode substituir o valor gerado definindo-o antes que a entidade seja marcada como Added:

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

Alguns serviços singleton agora estão no escopo

Problema de acompanhamento n.25084

Novo comportamento

Muitos serviços de consulta e alguns serviços em tempo de design que foram registrados como Singleton agora estão registrados como Scoped.

Por que

O tempo de vida teve que ser alterado para permitir que um novo recurso (DefaultTypeMapping) afetasse as consultas.

Os tempos de vida dos serviços de tempo de design foram ajustados para corresponder aos tempos de vida dos serviços em tempo de execução para evitar erros ao usar ambos.

Atenuações

Use TryAdd para registrar serviços do EF Core usando o tempo de vida padrão. Use TryAddProviderSpecificServices apenas para serviços que não são adicionados pelo EF.

Nova API de cache para extensões que adicionam ou substituem serviços

Problema de acompanhamento n.19152

Comportamento antigo

No EF Core 5, GetServiceProviderHashCode retornou long e foi usado diretamente como parte da chave de cache para o provedor de serviços.

Novo comportamento

GetServiceProviderHashCode agora retorna int e só é usado para calcular o código hash da chave de cache para o provedor de serviços.

Além disso, ShouldUseSameServiceProvider precisa ser implementado para indicar se o objeto atual representa a mesma configuração de serviço e, portanto, pode usar o mesmo provedor de serviço.

Por que

Apenas o uso de um código hash como parte da chave de cache resultou em colisões ocasionais que eram difíceis de diagnosticar e corrigir. O método adicional garante que o mesmo provedor de serviços seja usado somente quando apropriado.

Atenuações

Muitas extensões não expõem nenhuma opção que afete os serviços registrados e podem usar a seguinte implementação de ShouldUseSameServiceProvider:

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

Caso contrário, predicados adicionais devem ser adicionados para comparar todas as opções relevantes.

Novo procedimento de inicialização de modelo em tempo de design e instantâneo

Problema de acompanhamento n.22031

Comportamento antigo

No EF Core 5, as convenções específicas precisavam ser invocadas antes que o modelo de instantâneo estivesse pronto para ser usado.

Novo comportamento

O IModelRuntimeInitializer foi introduzido para ocultar algumas das etapas necessárias, e foi introduzido um modelo de tempo de execução que não tem todos os metadados de migrações, portanto, o modelo de tempo de design deve ser usado para diferenciação de modelos.

Por que

IModelRuntimeInitializer abstrai as etapas de finalização de modelo, de modo que elas agora podem ser alteradas sem mais alterações interruptivas para os usuários.

O modelo de tempo de execução otimizado foi introduzido para aprimorar o desempenho em tempo de execução. Ele tem várias otimizações, uma das quais é a remoção dos metadados que não são usados em tempo de execução.

Atenuações

O seguinte trecho de código ilustra como verificar se o modelo atual é diferente do modelo de instantâneo:

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

Este trecho de código mostra como implementar IDesignTimeDbContextFactory<TContext> criando um modelo externamente e chamando UseModel:

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex agora retorna um tipo diferente

Problema de acompanhamento n.24005

Comportamento antigo

No EF Core 5, HasIndex retornava IndexBuilder<TEntity>, em que TEntity é o tipo do proprietário.

Novo comportamento

HasIndex agora retorna IndexBuilder<TDependentEntity>, em que TDependentEntity é o tipo com proprietário.

Por que

O objeto construtor retornado não foi digitado corretamente.

Atenuações

A recompilação do assembly em relação à versão mais recente do EF Core será suficiente para corrigir quaisquer problemas causados por essa alteração.

DbFunctionBuilder.HasSchema(null) substitui [DbFunction(Schema = "schema")]

Problema de acompanhamento n.24228

Comportamento antigo

No EF Core 5, chamar HasSchema com o valor null não armazenava a fonte de configuração, portanto, DbFunctionAttribute era capaz de substituí-la.

Novo comportamento

Chamar HasSchema com o valor null agora armazena a fonte de configuração e impede que o atributo a substitua.

Por que

A configuração especificada com a API ModelBuilder não deve ser substituída por anotações de dados.

Atenuações

Remova a chamada HasSchema para permitir que o atributo configure o esquema.

Navegações pré-configuradas são substituídas por valores de consultas de banco de dados

Problema de acompanhamento n.23851

Comportamento antigo

As propriedades de navegação definidas como um objeto vazio foram deixadas inalteradas para acompanhamento de consultas, mas foram substituídas para consultas sem acompanhamento. Por exemplo, considere os seguintes tipos de entidade:

public class Foo
{
    public int Id { get; set; }

    public Bar Bar { get; set; } = new(); // Don't do this.
}

public class Bar
{
    public int Id { get; set; }
}

Uma consulta sem acompanhamento para Foo, incluindo Bar definiu Foo.Bar para a entidade consultada do banco de dados. Por exemplo, este código:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimiu Foo.Bar.Id = 1.

No entanto, a mesma execução de consulta para o acompanhamento não substituiu Foo.Bar pela entidade consultada do banco de dados. Por exemplo, este código:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimiu Foo.Bar.Id = 0.

Novo comportamento

No EF Core 6,0, o comportamento do controle de consultas com acompanhamento agora corresponde ao de consultas sem acompanhamento. Isso significa que tanto este código:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Quanto este código:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimem Foo.Bar.Id = 1.

Por que

Há dois motivos para realizar essa alteração:

  1. Garantir que as consultas com e sem acompanhamento tenham comportamento consistente.
  2. Quando um banco de dados é consultado, é razoável pressupor que o código do aplicativo deseja obter os valores armazenados no banco de dados.

Atenuações

Há duas atenuações:

  1. Não consulte objetos do banco de dados que não devem ser incluídos nos resultados. Por exemplo, nos trechos de código acima, não Include Foo.Bar se a instância de Bar não deve ser retornada do banco de dados e incluída nos resultados.
  2. Defina o valor da navegação após a consulta do banco de dados. Por exemplo, nos trechos de código acima, chame foo.Bar = new() depois de executar a consulta.

Além disso, considere não inicializar instâncias de entidade relacionadas para objetos padrão. Isso implica que a instância relacionada é uma nova entidade, não salva no banco de dados e sem valor de chave definido. Se, em vez disso, a entidade relacionada existir no banco de dados, o código em si será fundamentalmente de acordo com os dados armazenados no banco de dados.

Valores de cadeia de caracteres de enumeração desconhecidos no banco de dados não são convertidos para o padrão de enumeração quando consultados

Problema de acompanhamento n.24084

Comportamento antigo

As propriedades de enumeração podem ser mapeadas para colunas de cadeia de caracteres no banco de dados usando HasConversion<string>() ou EnumToStringConverter. Isso resulta na conversão de valores de cadeia de caracteres pelo EF Core na coluna para membros correspondentes do tipo enum do .NET. No entanto, se o valor da cadeia de caracteres não correspondia a um membro de enumeração, a propriedade era definida como o valor padrão para a enumeração.

Novo comportamento

Agora, o EF Core 6.0 lança um InvalidOperationException com a mensagem "Não é possível converter o valor da cadeia de caracteres '{value}' do banco de dados em qualquer valor na enumeração '{enumType}' mapeada."

Por que

A conversão para o valor padrão poderá resultar na corrupção do banco de dados se a entidade for salva posteriormente no banco de dados.

Atenuações

O ideal é garantir que a coluna do banco de dados contenha apenas valores válidos. Como alternativa, implemente um ValueConverter com o comportamento antigo.

DbFunctionBuilder.HasTranslation agora fornece os argumentos da função como IReadOnlyList em vez de IReadOnlyCollection

Problema de acompanhamento n.23565

Comportamento antigo

Ao configurar a conversão para uma função definida pelo usuário usando o método HasTranslation, os argumentos para a função foram fornecidos como IReadOnlyCollection<SqlExpression>.

Novo comportamento

No EF Core 6.0, os argumentos agora são fornecidos como IReadOnlyList<SqlExpression>.

Por que

O IReadOnlyList permite usar indexadores, portanto, agora os argumentos são mais fáceis de acessar.

Atenuações

Nenhum. IReadOnlyList implementa a interface IReadOnlyCollection, portanto, a transição deve ser simples.

O mapeamento de tabela padrão não é removido quando a entidade é mapeada para uma função com valor de tabela

Problema de acompanhamento n. 23408

Comportamento antigo

Quando uma entidade era mapeada para uma função com valor de tabela, seu mapeamento padrão para uma tabela era removido.

Novo comportamento

No EF Core 6.0, a entidade ainda será mapeada para uma tabela usando o mapeamento padrão, mesmo se ela também estiver mapeada para uma função com valor de tabela.

Por que

As funções com valor de tabela que retornam entidades geralmente são usadas como um auxiliar ou para encapsular uma operação que retorna uma coleção de entidades, em vez de uma substituição estrita da tabela inteira. Essa mudança visa um alinhamento maior com a provável intenção do usuário.

Atenuações

O mapeamento para uma tabela pode ser explicitamente desabilitado na configuração do modelo:

modelBuilder.Entity<MyEntity>().ToTable((string)null);

O dotnet-ef destina-se ao .NET 6

Problema de acompanhamento n.27787

Comportamento antigo

O comando dotnet-ef tem sido destinado ao .NET Core 3.1 há algum tempo. Isso permitia que você usasse uma versão mais recente da ferramenta sem instalar versões mais recentes do runtime do .NET.

Novo comportamento

No EF Core 6.0.6, a ferramenta dotnet-ef agora é direcionada ao .NET 6. Você ainda pode usar a ferramenta em projetos direcionados a versões mais antigas do .NET e .NET Core, mas precisará instalar o runtime do .NET 6 para executar a ferramenta.

Por que

O SDK do .NET 6.0 200 atualizou o comportamento de dotnet tool install em osx-arm64 para criar um shim osx-x64 para ferramentas destinadas ao .NET Core 3.1. Para manter uma experiência funcional padrão para o dotnet-ef, precisamos atualizá-lo para ser direcionado ao .NET 6.

Atenuações

Para executar o dotnet-ef sem instalar o runtime do .NET 6, você pode instalar uma versão mais antiga da ferramenta:

dotnet tool install dotnet-ef --version 3.1.*

As implementações de IModelCacheKeyFactory talvez precisem ser atualizadas para lidarem com cache em tempo de design

Problema de acompanhamento n.25154

Comportamento antigo

IModelCacheKeyFactory não tinha uma opção para armazenar em cache o modelo de tempo de design separadamente do modelo de runtime.

Novo comportamento

IModelCacheKeyFactory não tinha uma opção para armazenar em cache o modelo de tempo de design separadamente do modelo de runtime. Não implementar esse método pode resultar em uma exceção semelhante a:

System.InvalidOperationException: 'A configuração solicitada não está armazenada no modelo otimizado para leitura, use 'DbContext.GetService<IDesignTimeModel>().Model'.'

Por que

A implementação de modelos compilados exigia a separação dos modelos de tempo de design (usado ao compilar o modelo) e de runtime (usado ao executar consultas etc.). Se o código de runtime precisa de acesso às informações de tempo de design, o modelo de tempo de design precisa ser armazenado em cache.

Atenuações

Implemente a nova sobrecarga. Por exemplo:

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();

A navegação '{navigation}' foi ignorada de 'Include' na consulta, pois a correção a preencherá automaticamente. Se outras navegações forem especificadas em "Incluir" posteriormente, elas serão ignoradas. Não é permitido voltar à árvore de inclusão.

Problema de acompanhamento nº 4315

Comportamento antigo

O evento CoreEventId.NavigationBaseIncludeIgnored foi registrado como um aviso por padrão.

Novo comportamento

O evento CoreEventId.NavigationBaseIncludeIgnored foi registrado como um erro por padrão e faz com que uma exceção seja gerada.

Por que

Esses padrões de consulta não são permitidos, portanto, o EF Core agora é gerado para indicar que as consultas devem ser atualizadas.

Mitigações

O comportamento antigo pode ser restaurado configurando o evento como um aviso. Por exemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));