Alterações significativas no EF Core 8 (EF8)
Esta página documenta as alterações de API e comportamento que têm o potencial de interromper a atualização de aplicativos existentes do EF Core 7 para o EF Core 8. Certifique-se de revisar as alterações de quebra anteriores se estiver atualizando de uma versão anterior do EF Core:
Estrutura de Destino
O EF Core 8 tem como alvo o .NET 8. Os aplicativos destinados a versões mais antigas do .NET, .NET Core e .NET Framework precisarão ser atualizados para o .NET 8 de destino.
Resumo
Alterações de alto impacto
Contains
em consultas LINQ podem parar de funcionar em versões mais antigas do SQL Server
Comportamento antigo
EF tinha suporte especializado para consultas LINQ usando o operador Contains
sobre uma lista de valores parametrizada:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
Antes do EF Core 8.0, o EF inseria os valores parametrizados como constantes no SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Novo comportamento
A partir do EF Core 8.0, o EF agora gera SQL que é mais eficiente em muitos casos, mas não tem suporte no SQL Server 2014 e abaixo:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (
SELECT [n].[value]
FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
)
Observe que as versões mais recentes do SQL Server podem ser configuradas com um nível de compatibilidade mais antigo, tornando-as também incompatíveis com o novo SQL. Isso também pode ocorrer com um banco de dados SQL do Azure que foi migrado de uma instância anterior do SQL Server local, carregando o nível de compatibilidade antigo.
Porquê
A inserção de valores constantes no SQL cria muitos problemas de desempenho, derrotando o cache do plano de consulta e causando remoções desnecessárias de outras consultas. A nova conversão do EF Core 8.0 usa a função SQL Server OPENJSON
para transferir os valores como uma matriz JSON. Isso resolve os problemas de desempenho inerentes à técnica anterior; no entanto, a função OPENJSON
não está disponível no SQL Server 2014 e abaixo.
Para obter mais informações sobre essa alteração, consulte esta postagem de blog.
Atenuações
Se o seu banco de dados for SQL Server 2016 (13.x) ou mais recente, ou se você estiver usando o Azure SQL, verifique o nível de compatibilidade configurado do seu banco de dados por meio do seguinte comando:
SELECT name, compatibility_level FROM sys.databases;
Se o nível de compatibilidade estiver abaixo de 130 (SQL Server 2016), considere modificá-lo para um valor mais recente (documentação).
Caso contrário, se a versão do banco de dados realmente for mais antiga que o SQL Server 2016 ou estiver definida para um nível de compatibilidade antigo que você não pode alterar por algum motivo, você poderá configurar o EF para reverter para o SQL anterior ao 8.0 mais antigo. Se você estiver usando o EF 9, poderá usar o TranslateParameterizedCollectionsToConstantsrecém-introduzido:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())
Se você estiver usando o EF 8, poderá obter o mesmo efeito ao usar o SQL Server configurando o nível de compatibilidade SQL do EF:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Possíveis regressões de desempenho de consulta em torno de Contains
em consultas LINQ
Comportamento antigo
EF tinha suporte especializado para consultas LINQ usando o operador Contains
sobre uma lista de valores parametrizável.
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
Antes do EF Core 8.0, o EF inseria os valores parametrizados como constantes no SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Novo comportamento
A partir do EF Core 8.0, o EF agora gera o seguinte:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (
SELECT [n].[value]
FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
)
No entanto, após o lançamento do EF 8, descobriu-se que, embora o novo SQL seja mais eficiente para a maioria dos casos, ele pode ser dramaticamente menos eficiente em uma minoria de casos, causando até mesmo tempos limite de consulta em alguns casos
Atenuações
Se estiver a usar o EF 9, pode usar o TranslateParameterizedCollectionsToConstants recém-introduzido para reverter a tradução de Contains
para todas as consultas para restaurar o comportamento anterior ao 8.0:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())
Se você estiver usando o EF 8, poderá obter o mesmo efeito ao usar o SQL Server configurando o nível de compatibilidade SQL do EF:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Finalmente, é possível controlar a tradução para cada consulta individualmente usando EF.Constant da seguinte forma:
var blogs = await context.Blogs
.Where(b => EF.Constant(names).Contains(b.Name))
.ToArrayAsync();
Enums em JSON são armazenados como ints em vez de strings por padrão
Comportamento antigo
No EF7, os enums mapeados para JSON são, por padrão, armazenados como valores de texto nos ficheiros JSON.
Novo comportamento
A partir do EF Core 8.0, o EF agora, por padrão, mapeia enums para valores inteiros no documento JSON.
Porquê
O EF sempre mapeou enums para uma coluna numérica como padrão em bancos de dados relacionais. Como o EF oferece suporte a consultas em que os valores de JSON interagem com valores de colunas e parâmetros, é importante que os valores em JSON correspondam aos valores na coluna não-JSON.
Atenuações
Para continuar usando cadeias de caracteres, configure a propriedade enum com uma conversão. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}
Ou, para todas as propriedades do tipo enum::
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}
Alterações de impacto médio
SQL Server date
e time
agora criam estrutura para .NET DateOnly
e TimeOnly
Comportamento antigo
Anteriormente, ao criar um scaffold em um banco de dados do SQL Server com colunas date
ou time
, o EF gerava propriedades de entidade com os tipos DateTime e TimeSpan.
Novo comportamento
A partir do EF Core 8.0, date
e time
são gerados como DateOnly e TimeOnly.
Porquê
DateOnly e TimeOnly foram introduzidas no .NET 6.0 e são uma combinação perfeita para mapear os tipos de data e hora do banco de dados.
DateTime contém notavelmente um componente de tempo que não é usado e pode causar confusão ao mapeá-lo para date
, e TimeSpan representa um intervalo de tempo - possivelmente incluindo dias - em vez de uma hora do dia em que um evento ocorre. Usar os novos tipos evita bugs e confusão, e fornece clareza de intenção.
Atenuações
Essa alteração afeta apenas os utilizadores que regularmente reconstroem o scaffold da sua base de dados num modelo de código EF ("fluxo de banco de dados primeiro").
Recomenda-se reagir a essa alteração modificando seu código para usar os tipos DateOnly e TimeOnly recém-estruturados. No entanto, se isso não for possível, você pode editar os modelos de andaimes para reverter para o mapeamento anterior. Para fazer isso, configure os modelos conforme descrito em esta página. Em seguida, edite o arquivo EntityType.t4
, localize onde as propriedades da entidade são geradas (pesquise property.ClrType
) e altere o código para o seguinte:
var clrType = property.GetColumnType() switch
{
"date" when property.ClrType == typeof(DateOnly) => typeof(DateTime),
"date" when property.ClrType == typeof(DateOnly?) => typeof(DateTime?),
"time" when property.ClrType == typeof(TimeOnly) => typeof(TimeSpan),
"time" when property.ClrType == typeof(TimeOnly?) => typeof(TimeSpan?),
_ => property.ClrType
};
usings.AddRange(code.GetRequiredUsings(clrType));
var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType;
var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType;
#>
public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
As colunas booleanas com um valor gerado pelo banco de dados não são mais estruturadas como anuláveis
Problema de rastreamento #15070
Comportamento antigo
Anteriormente, colunas não anuláveis de bool
com uma restrição padrão do banco de dados eram geradas automaticamente como propriedades de bool?
passíveis de ser nulas.
Novo comportamento
A partir do EF Core 8.0, as colunas de bool
não anuláveis são sempre estruturadas como propriedades não anuláveis.
Porquê
Uma propriedade bool
não terá seu valor enviado para o banco de dados se esse valor for false
, que é o padrão CLR. Se o banco de dados tiver um valor padrão de true
para a coluna, mesmo que o valor da propriedade seja false
, o valor no banco de dados terminará como true
. No entanto, no EF8, a sentinela usada para determinar se uma propriedade tem um valor pode ser alterada. Isso é feito automaticamente para bool
propriedades com um valor gerado pelo banco de dados de true
, o que significa que não é mais necessário andaimear as propriedades como anuláveis.
Atenuações
Essa alteração afeta apenas os usuários que regularmente refazem o scaffold de seu banco de dados em um modelo de código EF ("fluxo de banco de dados primeiro").
É recomendável reagir a essa alteração modificando seu código para usar a propriedade bool não anulável. No entanto, se isso não for possível, você pode editar os modelos de andaimes para reverter para o mapeamento anterior. Para fazer isso, configure os modelos como descrito nesta página . Em seguida, edite o arquivo EntityType.t4
, localize onde as propriedades da entidade são geradas (pesquise property.ClrType
) e altere o código para o seguinte:
#>
var propertyClrType = property.ClrType != typeof(bool)
|| (property.GetDefaultValueSql() == null && property.GetDefaultValue() != null)
? property.ClrType
: typeof(bool?);
#>
public <#= code.Reference(propertyClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
<#
Alterações de baixo impacto
Os métodos SQLite Math
agora se traduzem para SQL
Comportamento antigo
Anteriormente, apenas os métodos Abs, Max, Min e Round em Math
eram traduzidos para SQL. Todos os outros membros seriam avaliados no cliente se aparecessem na expressão Select final de uma consulta.
Novo comportamento
No EF Core 8.0, todos os métodos Math
com funções matemáticas SQLite correspondentes são traduzidos para SQL.
Essas funções matemáticas foram habilitadas na biblioteca SQLite nativa que fornecemos por padrão (por meio de nossa dependência do pacote NuGet SQLitePCLRaw.bundle_e_sqlite3). Eles também foram habilitados na biblioteca fornecida pela SQLitePCLRaw.bundle_e_sqlcipher. Se você estiver usando uma dessas bibliotecas, seu aplicativo não deve ser afetado por essa alteração.
Há uma chance, no entanto, de que os aplicativos, incluindo a biblioteca SQLite nativa por outros meios, podem não habilitar as funções matemáticas. Nesses casos, os métodos Math
serão traduzidos para SQL e encontrarão nenhuma função erros quando executados.
Porquê
SQLite adicionou funções matemáticas incorporadas na versão 3.35.0. Apesar de estarem desativados por padrão, eles se tornaram generalizados o suficiente para que decidimos fornecer traduções padrão para eles em nosso provedor EF Core SQLite.
Também colaboramos com Eric Sink no projeto SQLitePCLRaw para habilitar funções matemáticas em todas as bibliotecas SQLite nativas fornecidas como parte desse projeto.
Atenuações
A maneira mais simples de corrigir quebras é, quando possível, habilitar a função matemática na biblioteca SQLite nativa especificando a opção de compilação SQLITE_ENABLE_MATH_FUNCTIONS.
Se você não controlar a compilação da biblioteca nativa, também poderá corrigir quebras criando as funções por conta própria em tempo de execução usando as APIs de Microsoft.Data.Sqlite
sqliteConnection
.CreateFunction<double, double, double>(
"pow",
Math.Pow,
isDeterministic: true);
Como alternativa, você pode forçar a avaliação do cliente dividindo a expressão Select em duas partes separadas por AsEnumerable
.
// Before
var query = dbContext.Cylinders
.Select(
c => new
{
Id = c.Id
// May throw "no such function: pow"
Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
});
// After
var query = dbContext.Cylinders
// Select the properties you'll need from the database
.Select(
c => new
{
c.Id,
c.Radius,
c.Height
})
// Switch to client-eval
.AsEnumerable()
// Select the final results
.Select(
c => new
{
Id = c.Id,
Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
});
ITypeBase substitui IEntityType em algumas APIs
Questão de Rastreamento #13947
Comportamento antigo
Anteriormente, todos os tipos estruturais mapeados eram tipos de entidade.
Novo comportamento
Com a introdução de tipos complexos no EF8, algumas APIs que antes usavam um IEntityType
agora usam ITypeBase
para que as APIs possam ser usadas com entidades ou tipos complexos. Isto inclui:
-
IProperty.DeclaringEntityType
está agora obsoleta eIProperty.DeclaringType
deve ser utilizada em vez disso. -
IEntityTypeIgnoredConvention
está agora obsoleta eITypeIgnoredConvention
deve ser utilizada em vez disso. -
IValueGeneratorSelector.Select
agora aceita umITypeBase
que pode ser, mas não precisa ser umIEntityType
.
Porquê
Com a introdução de tipos complexos no EF8, essas APIs podem ser usadas com IEntityType
ou IComplexType
.
Atenuações
As APIs antigas estão obsoletas, mas não serão removidas até o EF10. O código deve ser atualizado para usar as novas APIs o mais rápido possível.
As expressões ValueConverter e ValueComparer devem usar APIs públicas para o modelo compilado
Comportamento antigo
Anteriormente, ValueConverter
e ValueComparer
definições não eram incluídas no modelo compilado e, portanto, podiam conter código arbitrário.
Novo comportamento
O EF agora extrai as expressões dos objetos ValueConverter
e ValueComparer
e inclui esses C# no modelo compilado. Isso significa que essas expressões devem usar apenas a API pública.
Porquê
A equipe do EF está gradualmente movendo mais construções para o modelo compilado para suportar o uso do EF Core com AOT no futuro.
Atenuações
Torne públicas as APIs usadas pelo comparador. Por exemplo, considere este conversor simples:
public class MyValueConverter : ValueConverter<string, byte[]>
{
public MyValueConverter()
: base(v => ConvertToBytes(v), v => ConvertToString(v))
{
}
private static string ConvertToString(byte[] bytes)
=> ""; // ... TODO: Conversion code
private static byte[] ConvertToBytes(string chars)
=> Array.Empty<byte>(); // ... TODO: Conversion code
}
Para usar este conversor em um modelo compilado com EF8, os métodos ConvertToString
e ConvertToBytes
devem ser tornados públicos. Por exemplo:
public class MyValueConverter : ValueConverter<string, byte[]>
{
public MyValueConverter()
: base(v => ConvertToBytes(v), v => ConvertToString(v))
{
}
public static string ConvertToString(byte[] bytes)
=> ""; // ... TODO: Conversion code
public static byte[] ConvertToBytes(string chars)
=> Array.Empty<byte>(); // ... TODO: Conversion code
}
ExcludeFromMigrations não exclui mais outras tabelas em uma hierarquia TPC
Comportamento antigo
Anteriormente, o uso de ExcludeFromMigrations
em uma tabela em uma hierarquia TPC também excluía outras tabelas na hierarquia.
Novo comportamento
A partir do EF Core 8.0, ExcludeFromMigrations
não afeta outras tabelas.
Porquê
O comportamento antigo era um bug e impedia que as migrações fossem usadas para gerenciar hierarquias entre projetos.
Atenuações
Use ExcludeFromMigrations
explicitamente em qualquer outra tabela que deva ser excluída.
Chaves de inteiros não sombreadas são persistidas em documentos do Cosmos
Problema de rastreamento #31664
Comportamento antigo
Anteriormente, as propriedades inteiras não sombreadas que correspondem aos critérios para ser uma propriedade de chave sintetizada não eram persistidas no documento JSON, mas eram ressintetizadas na saída.
Novo comportamento
A partir do EF Core 8.0, essas propriedades agora persistem.
Porquê
O comportamento antigo era um bug e impedia que propriedades que correspondessem aos critérios-chave sintetizados fossem persistidas no Cosmos.
Atenuações
Exclua a propriedade do modelo se seu valor não deve ser persistente.
Além disso, pode-se desabilitar esse comportamento inteiramente ao definir a alternância AppContext Microsoft.EntityFrameworkCore.Issue31664
como true
. Consulte AppContext para utilizadores de bibliotecas para mais detalhes.
AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);
O modelo relacional é gerado no modelo compilado
Comportamento antigo
Anteriormente, o modelo relacional era calculado em tempo de execução, mesmo quando se usava um modelo compilado.
Novo comportamento
A partir do EF Core 8.0, o modelo relacional faz parte do modelo compilado gerado. No entanto, para modelos particularmente grandes, o arquivo gerado pode falhar ao compilar.
Porquê
Isso foi feito para melhorar ainda mais o tempo de inicialização.
Atenuações
Edite o arquivo de *ModelBuilder.cs
gerado e remova a linha AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
, bem como o método CreateRelationalModel()
.
Os andaimes podem gerar nomes de navegação diferentes
Comportamento antigo
Anteriormente, ao estruturar um DbContext
e tipos de entidade de um banco de dados existente, os nomes de navegação para relacionamentos às vezes eram derivados de um prefixo comum de vários nomes de coluna de chave estrangeira.
Novo comportamento
A partir do EF Core 8.0, prefixos comuns de nomes de colunas de uma chave estrangeira composta não são mais usados para gerar nomes de navegação.
Porquê
Esta é uma regra de nomenclatura obscura que às vezes gera nomes muito pobres como, S
, Student_
, ou mesmo apenas _
. Sem essa regra, nomes estranhos não são mais gerados, e as convenções de nomenclatura para navegações também são simplificadas, tornando mais fácil entender e prever quais nomes serão gerados.
Atenuações
As EF Core Power Tools têm a opção de continuar a gerar navegações no modo antigo. Como alternativa, o código gerado pode ser totalmente personalizado usando modelos T4 . Isso pode ser usado para exemplificar as propriedades de chave estrangeira em relações de estruturação e aplicar qualquer regra apropriada para o seu código, a fim de gerar os nomes de navegação necessários.
Os discriminadores têm agora um comprimento máximo
Comportamento antigo
Anteriormente, as colunas discriminadoras criadas para o mapeamento de herança TPH eram configuradas como nvarchar(max)
no SQL Server/Azure SQL, ou como o tipo de cadeia de caracteres sem limite equivalente em outros bancos de dados.
Novo comportamento
A partir do EF Core 8.0, as colunas do discriminador são criadas com um comprimento máximo que abrange todos os valores de discriminador conhecidos. O EF gerará uma migração para fazer essa alteração. No entanto, se a coluna discriminadora for restringida de alguma forma - por exemplo, como parte de um índice - a AlterColumn
criada por Migrações poderá falhar.
Porquê
nvarchar(max)
colunas são ineficientes e desnecessárias quando os comprimentos de todos os valores possíveis são conhecidos.
Atenuações
O tamanho da coluna pode ser explicitamente ilimitado:
modelBuilder.Entity<Foo>()
.Property<string>("Discriminator")
.HasMaxLength(-1);
Os valores de chave do SQL Server são comparados sem distinção entre maiúsculas e minúsculas
Problema de rastreamento #27526
Comportamento antigo
Anteriormente, ao controlar entidades com chaves de cadeia de caracteres com os provedores de banco de dados SQL do SQL Server/Azure, os valores de chave eram comparados usando o comparador ordinal padrão que diferencia maiúsculas de minúsculas do .NET.
Novo comportamento
A partir do EF Core 8.0, os valores de chave de cadeia de caracteres SQL do SQL Server/Azure são comparados usando o comparador ordinal padrão que não diferencia maiúsculas de minúsculas do .NET.
Porquê
Por padrão, o SQL Server utiliza comparações que não diferenciam maiúsculas de minúsculas ao comparar valores de chave estrangeira com valores correspondentes de chave principal. Isso significa que, quando o EF usa comparações que diferenciam maiúsculas de minúsculas, ele pode não conectar uma chave estrangeira a uma chave principal quando deveria.
Atenuações
As comparações que diferenciam maiúsculas de minúsculas podem ser usadas definindo um ValueComparer
personalizado. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var comparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.Ordinal),
v => v.GetHashCode(),
v => v);
modelBuilder.Entity<Blog>()
.Property(e => e.Id)
.Metadata.SetValueComparer(comparer);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
});
}
Várias chamadas AddDbContext são aplicadas em ordem diferente
Comportamento antigo
Anteriormente, quando várias chamadas para AddDbContext
, AddDbContextPool
, AddDbContextFactory
ou AddPooledDbContextFactor
eram feitas com o mesmo tipo de contexto, mas configuração conflitante, a primeira venceu.
Novo comportamento
A partir do EF Core 8.0, a configuração da última invocação terá precedência.
Porquê
Isso foi alterado para ser consistente com o novo método ConfigureDbContext
que pode ser usado para adicionar configuração antes ou depois dos métodos Add*
.
Atenuações
Inverta a ordem das chamadas Add*
.
EntityTypeAttributeConventionBase substituído por TypeAttributeConventionBase
Novo comportamento
No EF Core 8.0, EntityTypeAttributeConventionBase
foi renomeado para TypeAttributeConventionBase
.
Porquê
TypeAttributeConventionBase
representa melhor a funcionalidade, pois agora pode ser usado para tipos complexos e tipos de entidade.
Atenuações
Substitua as utilizações de EntityTypeAttributeConventionBase
por TypeAttributeConventionBase
.