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
* 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:
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(); });
Verifique se o dependente contém pelo menos uma propriedade necessária.
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.
Azure Cosmos DB: os tipos de entidade relacionados são descobertos como com proprietário
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 | |
SetNull | SET NULL |
ClientSetNull |
As alterações para Scaffolding são conforme mostrado a seguir.
ON DELETE | OnDelete() |
---|---|
NO ACTION | ClientSetNull |
RESTRICT | |
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:
- Garantir que as consultas com e sem acompanhamento tenham comportamento consistente.
- 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:
- 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 deBar
não deve ser retornada do banco de dados e incluída nos resultados. - 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.
NavigationBaseIncludeIgnored
agora é um erro por padrã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));