Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
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 9 para o EF Core 10. Certifique-se de revisar as alterações disruptivas anteriores se estiver a atualizar a partir de uma versão anterior do EF Core.
- Mudanças significativas no EF Core 9
- Mudanças significativas no EF Core 8
- Mudanças significativas no EF Core 7
- Mudanças significativas no EF Core 6
Resumo
Observação
Se estiver a usar Microsoft.Data.Sqlite, consulte a seção separada abaixo sobre alterações incompatíveis em Microsoft.Data.Sqlite.
Alterações de impacto médio
As ferramentas de EF agora exigem que a estrutura seja especificada para projetos multidirecionados
Comportamento antigo
Anteriormente, as ferramentas de EF (dotnet-ef) podiam ser usadas em projetos direcionados a múltiplos frameworks sem especificar qual a framework a utilizar.
Novo comportamento
A partir do EF Core 10.0, ao executar ferramentas EF num projeto que tem como alvo múltiplos frameworks (usando <TargetFrameworks> em vez de <TargetFramework>), deve especificar explicitamente qual framework de destino usar com a --framework opção. Sem esta opção, será lançado o seguinte erro:
O projeto visa múltiplos frameworks. Use a opção --framework para especificar qual framework alvo usar.
Porquê
No EF Core 10, as ferramentas começaram a depender da ResolvePackageAssets tarefa MSBuild para obter informações mais precisas sobre dependências de projetos. No entanto, esta tarefa não está disponível se o projeto estiver a direcionar múltiplos frameworks-alvo (TFMs). A solução exige que os utilizadores escolham qual o framework a utilizar.
Atenuações
Ao executar qualquer comando de ferramentas EF num projeto que vise múltiplos frameworks, especifique o framework de destino usando a --framework opção. Por exemplo:
dotnet ef migrations add MyMigration --framework net9.0
dotnet ef database update --framework net9.0
dotnet ef migrations script --framework net9.0
Se o teu ficheiro de projeto for assim:
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
Terás de escolher um dos frameworks (por exemplo, net9.0) ao executar as ferramentas do EF.
Alterações de baixo impacto
O nome do aplicativo agora é injetado na cadeia de conexão
Problema de rastreamento #35730
Novo comportamento
Quando uma cadeia de conexão sem um Application Name é passada para o EF, o EF agora insere um Application Name contendo informações anônimas sobre as versões EF e SqlClient que estão sendo usadas. Na grande maioria dos casos, isso não afeta a aplicação de forma alguma, mas pode afetar o comportamento em alguns casos extremos. Por exemplo, se você se conectar ao mesmo banco de dados com EF e outra tecnologia de acesso a dados não-EF (por exemplo, Dapper, ADO.NET), o SqlClient usará um pool de conexões interno diferente, pois o EF agora usará uma cadeia de conexão diferente e atualizada (uma em que Application Name foi injetada). Se esse tipo de acesso misto for feito dentro de um TransactionScope, isso pode causar escalonamento para uma transação distribuída onde anteriormente nenhuma era necessária, devido ao uso de duas cadeias de conexão que o SqlClient identifica como dois bancos de dados distintos.
Atenuações
Para mitigar, basta definir um Application Name na cadeia de conexão. Uma vez que uma é definida, o EF não a substitui e a cadeia de conexão original é preservada exatamente como está.
Tipo de dados json do SQL Server usado por padrão no SQL do Azure e nível de compatibilidade 170
Problema de rastreamento #36372
Comportamento antigo
Anteriormente, ao mapear coleções primitivas ou tipos de propriedade para JSON no banco de dados, o provedor do SQL Server armazenava os dados JSON em uma nvarchar(max) coluna:
public class Blog
{
// ...
// Primitive collection, mapped to nvarchar(max) JSON column
public string[] Tags { get; set; }
// Owned entity type mapped to nvarchar(max) JSON column
public List<Post> Posts { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().OwnsMany(b => b.Posts, b => b.ToJson());
}
Para o acima, o EF gerou anteriormente a seguinte tabela:
CREATE TABLE [Blogs] (
...
[Tags] nvarchar(max),
[Posts] nvarchar(max)
);
Novo comportamento
Com o EF 10, se você configurar o EF com UseAzureSql (consulte a documentação) ou configurar o EF com um nível de compatibilidade igual ou superior a 170 (consulte a documentação), o EF será mapeado para o novo tipo de dados JSON:
CREATE TABLE [Blogs] (
...
[Tags] json
[Posts] json
);
Embora o novo tipo de dados JSON seja a maneira recomendada de armazenar dados JSON no SQL Server no futuro, pode haver algumas diferenças comportamentais durante a transição do , e alguns formulários de consulta específicos podem não ser suportados nvarchar(max). Por exemplo, o SQL Server não oferece suporte ao operador DISTINCT em matrizes JSON e as consultas que tentarem fazer isso falharão.
Observe que, se você tiver uma tabela existente e estiver usando UseAzureSqlo , a atualização para o EF 10 fará com que seja gerada uma migração que alterará todas as colunas JSON existentes nvarchar(max) para json. Esta operação de alteração é suportada e deve ser aplicada sem problemas e sem problemas, mas é uma alteração não trivial para o seu banco de dados.
Porquê
O novo tipo de dados JSON introduzido pelo SQL Server é uma maneira superior de 1ª classe de armazenar e interagir com dados JSON no banco de dados; Traz, nomeadamente, melhorias significativas de desempenho (ver documentação). Todos os aplicativos que usam o Banco de Dados SQL do Azure ou o SQL Server 2025 são incentivados a migrar para o novo tipo de dados JSON.
Atenuações
Se você estiver direcionando o Banco de Dados SQL do Azure e não desejar fazer a transição para o novo tipo de dados JSON imediatamente, poderá configurar o EF com um nível de compatibilidade inferior a 170:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseAzureSql("<connection string>", o => o.UseCompatibilityLevel(160));
}
Se você estiver direcionando o SQL Server local, o nível de compatibilidade padrão com UseSqlServer é atualmente 150 (SQL Server 2019), portanto, o tipo de dados JSON não é usado.
Como alternativa, você pode definir explicitamente o tipo de coluna em propriedades específicas como :nvarchar(max)
public class Blog
{
public string[] Tags { get; set; }
public List<Post> Posts { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().PrimitiveCollection(b => b.Tags).HasColumnType("nvarchar(max)");
modelBuilder.Entity<Blog>().OwnsMany(b => b.Posts, b => b.ToJson().HasColumnType("nvarchar(max)"));
modelBuilder.Entity<Blog>().ComplexProperty(e => e.Posts, b => b.ToJson());
}
As coleções parametrizadas agora usam múltiplos parâmetros por defeito
Comportamento antigo
No EF Core 9 e anteriores, coleções parametrizadas em consultas LINQ (como as usadas com .Contains()) eram traduzidas para SQL usando um parâmetro de array JSON por defeito. Considere a seguinte consulta:
int[] ids = [1, 2, 3];
var blogs = await context.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();
No SQL Server, isto gerava o seguinte SQL:
@__ids_0='[1,2,3]'
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (
SELECT [i].[value]
FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
)
Novo comportamento
A partir do EF Core 10.0, as coleções parametrizadas são agora traduzidas usando múltiplos parâmetros escalares por padrão.
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (@ids1, @ids2, @ids3)
Porquê
A nova tradução padrão fornece ao planeador de consultas informação de cardinalidade sobre a coleção, o que pode levar a melhores planos de consulta em muitos cenários. A abordagem de múltiplos parâmetros equilibra a eficiência do cache do plano (por parametrização) e a otimização de consultas (fornecendo cardinalidade).
No entanto, diferentes cargas de trabalho podem beneficiar de estratégias de tradução distintas, dependendo do tamanho das coleções, padrões de consulta e características da base de dados.
Observação
Embora a nova tradução padrão não cause qualquer alteração comportamental ou regressão de desempenho na maioria dos casos, a alteração na forma como as consultas são traduzidas para SQL pode ter consequências negativas em alguns cenários.
Aplicações que foram construídas com EF Core 8 ou 9, e que dependem das características de desempenho da tradução de parâmetros do array JSON (usando OPENJSON ou funções específicas de base de dados semelhantes), podem experienciar diferenças de desempenho ao atualizar para o EF Core 10. Isto é especialmente relevante para consultas com grandes coleções ou padrões específicos de consulta que beneficiaram da estratégia de tradução anterior.
Se experienciar regressões de desempenho após a atualização, considere usar as estratégias de mitigação abaixo para reverter ao comportamento anterior globalmente ou para consultas específicas.
Atenuações
Se encontrar problemas com o novo comportamento padrão (como regressões de desempenho), pode configurar o modo de tradução globalmente:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("<CONNECTION STRING>",
o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
Os modos disponíveis são:
-
ParameterTranslationMode.MultipleParameters- O novo padrão (múltiplos parâmetros escalares) -
ParameterTranslationMode.Constant- Valores em linha como constantes (comportamento padrão pré-EF8) -
ParameterTranslationMode.Parameter- Utiliza parâmetro de array JSON (padrão EF8-9)
Também pode controlar a tradução por consulta:
// Use constants instead of parameters for this specific query
var blogs = await context.Blogs
.Where(b => EF.Constant(ids).Contains(b.Id))
.ToListAsync();
// Use a single parameter (e.g. JSON parameter with OPENJSON) instead of parameters for this specific query
var blogs = await context.Blogs
.Where(b => EF.Parameter(ids).Contains(b.Id))
.ToListAsync();
// Use multiple scalar parameters for this specific query. This is the default in EF 10, but is useful if the default was changed globally:
var blogs = await context.Blogs
.Where(b => EF.MultipleParameters(ids).Contains(b.Id))
.ToListAsync();
Para mais informações sobre a tradução de coleções parametrizadas, consulte a documentação.
ExecuteUpdateAsync agora aceita um lambda regular e sem expressão
Problema de rastreamento #32018
Comportamento antigo
Anteriormente, ExecuteUpdate aceitava um argumento de árvore de expressão (Expression<Func<...>>) para os definidores de coluna.
Novo comportamento
A partir do EF Core 10.0, ExecuteUpdate agora aceita um argumento não-expressão (Func<...>) para os definidores de coluna. Se estiver a criar árvores de expressão para gerar dinamicamente o argumento dos definidores de coluna, o seu código já não irá compilar, mas pode ser substituído por uma alternativa muito mais simples (veja abaixo).
Porquê
O fato de o parâmetro columns setters ser uma árvore de expressão tornou bastante difícil fazer a construção dinâmica dos setters de coluna, onde alguns setters só estão presentes com base em alguma condição (veja Mitigações abaixo para um exemplo).
Atenuações
O código que estava criando árvores de expressão para criar dinamicamente o argumento dos setters de coluna precisará ser reescrito - mas o resultado será muito mais simples. Por exemplo, vamos supor que queremos atualizar as visualizações de um blog, mas condicionalmente também seu nome. Como o argumento setters era uma árvore de expressão, códigos como os seguintes precisavam ser escritos:
// Base setters - update the Views only
Expression<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>> setters =
s => s.SetProperty(b => b.Views, 8);
// Conditionally add SetProperty(b => b.Name, "foo") to setters, based on the value of nameChanged
if (nameChanged)
{
var blogParameter = Expression.Parameter(typeof(Blog), "b");
setters = Expression.Lambda<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>>(
Expression.Call(
instance: setters.Body,
methodName: nameof(SetPropertyCalls<Blog>.SetProperty),
typeArguments: [typeof(string)],
arguments:
[
Expression.Lambda<Func<Blog, string>>(Expression.Property(blogParameter, nameof(Blog.Name)), blogParameter),
Expression.Constant("foo")
]),
setters.Parameters);
}
await context.Blogs.ExecuteUpdateAsync(setters);
Criar árvores de expressão manualmente é complicado e propenso a erros, e tornou esse cenário comum muito mais difícil do que deveria ter sido. A partir do EF 10, agora você pode escrever o seguinte:
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged)
{
s.SetProperty(b => b.Name, "foo");
}
});
Os nomes de colunas de tipo complexo agora são unificados
Problema de rastreamento #4970
Comportamento antigo
Anteriormente, ao mapear tipos complexos para colunas de tabela, se várias propriedades em diferentes tipos complexos tivessem o mesmo nome de coluna, elas compartilhariam silenciosamente a mesma coluna.
Novo comportamento
A partir do EF Core 10.0, os nomes de colunas de tipos complexos são unificados acrescentando um número no final se existir outra coluna com o mesmo nome na tabela.
Porquê
Isso evita a corrupção de dados que pode ocorrer quando várias propriedades são mapeadas involuntariamente para a mesma coluna.
Atenuações
Se precisar de múltiplas propriedades para partilhar a mesma coluna, configure-as explicitamente usando Property e HasColumnName:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
b.ComplexProperty(c => c.BillingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
});
As propriedades de tipo complexo aninhado usam caminho completo em nomes de coluna
Comportamento antigo
Anteriormente, as propriedades em tipos complexos aninhados eram mapeadas para colunas usando apenas o nome do tipo declarante. Por exemplo, EntityType.Complex.NestedComplex.Property foi mapeado para a coluna NestedComplex_Property.
Novo comportamento
A partir do EF Core 10.0, as propriedades de tipos complexos aninhados utilizam o caminho completo até à propriedade como parte integrante do nome da coluna. Por exemplo, EntityType.Complex.NestedComplex.Property agora está mapeado para a coluna Complex_NestedComplex_Property.
Porquê
Isso proporciona uma melhor exclusividade dos nomes das colunas e torna mais claro qual propriedade corresponde a qual coluna.
Atenuações
Se precisares de manter os nomes das colunas antigas, configura-os explicitamente usando Property e HasColumnName:
modelBuilder.Entity<EntityType>()
.ComplexProperty(e => e.Complex)
.ComplexProperty(o => o.NestedComplex)
.Property(c => c.Property)
.HasColumnName("NestedComplex_Property");
A assinatura de IDiscriminatorPropertySetConvention foi alterada
Comportamento antigo
Anteriormente, IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet tomava IConventionEntityTypeBuilder como parâmetro.
Novo comportamento
A partir do EF Core 10.0, a assinatura do método foi alterada para take IConventionTypeBaseBuilder em vez de IConventionEntityTypeBuilder.
Porquê
Essa alteração permite que a convenção trabalhe com tipos de entidade e tipos complexos.
Atenuações
Atualize suas implementações de convenção personalizadas para usar a nova assinatura:
public virtual void ProcessDiscriminatorPropertySet(
IConventionTypeBaseBuilder typeBaseBuilder, // Changed from IConventionEntityTypeBuilder
string name,
Type type,
MemberInfo memberInfo,
IConventionContext<IConventionProperty> context)
Os métodos de IRelationalCommandDiagnosticsLogger adicionam o parâmetro logCommandText.
Comportamento antigo
Anteriormente, métodos como IRelationalCommandDiagnosticsLoggerCommandReaderExecuting, CommandReaderExecuted, CommandScalarExecuting, e outros aceitavam um command parâmetro que representava o comando da base de dados a ser executado.
Novo comportamento
A partir do EF Core 10.0, estes métodos requerem agora um parâmetro adicional logCommandText . Este parâmetro contém o texto do comando SQL que será registado, que pode ter dados sensíveis ocultos quando EnableSensitiveDataLogging não está ativado.
Porquê
Esta alteração suporta a nova funcionalidade de redigir as constantes embutidas nos registos por predefinição. Quando EF integra valores de parâmetros em SQL (por exemplo, ao usar EF.Constant()), esses valores são agora removidos dos registos, a menos que o registo de dados sensíveis esteja explicitamente ativado. O logCommandText parâmetro fornece o SQL censurado para fins de registo, enquanto o command parâmetro contém o SQL real que é executado.
Atenuações
Se tiver uma implementação personalizada de IRelationalCommandDiagnosticsLogger, terá de atualizar as assinaturas dos seus métodos para incluir o novo logCommandText parâmetro. Por exemplo:
public InterceptionResult<DbDataReader> CommandReaderExecuting(
IRelationalConnection connection,
DbCommand command,
DbContext context,
Guid commandId,
Guid connectionId,
DateTimeOffset startTime,
string logCommandText) // New parameter
{
// Use logCommandText for logging purposes
// Use command for execution-related logic
}
O logCommandText parâmetro contém o SQL a registar (com as constantes integradas potencialmente censuradas), enquanto command.CommandText contém o SQL real que será executado contra a base de dados.
Microsoft.Data.Sqlite alterações disruptivas
Resumo
Alterações de alto impacto
Ao usar GetDateTimeOffset sem um deslocamento, agora pressupõe-se padrão UTC.
Problema de rastreamento #36195
Comportamento antigo
Anteriormente, ao usar GetDateTimeOffset num carimbo de data/hora textual sem deslocamento (por exemplo, 2014-04-15 10:47:16), Microsoft.Data.Sqlite assumia que o valor estava na hora local. Ou seja, o valor foi analisado como 2014-04-15 10:47:16+02:00 (assumindo que o fuso horário local era UTC+2).
Novo comportamento
A partir de Microsoft.Data.Sqlite 10.0, ao usar GetDateTimeOffset em um carimbo de data/hora textual que não tenha um deslocamento, Microsoft.Data.Sqlite assumirá que o valor está em UTC.
Porquê
Destina-se a alinhar com o comportamento do SQLite, onde timestamps sem desvio são tratados como UTC.
Atenuações
O código deve ser ajustado em conformidade.
Como último recurso/temporário, você pode reverter para o comportamento anterior definindo Microsoft.Data.Sqlite.Pre10TimeZoneHandling a opção AppContext como true, consulte AppContext para consumidores de biblioteca para obter mais detalhes.
AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);
Escrever DateTimeOffset na coluna REAL agora escreve em UTC
Problema de rastreamento #36195
Comportamento antigo
Anteriormente, quando se escrevia um valor DateTimeOffset numa coluna REAL, o Microsoft.Data.Sqlite escrevia o valor sem considerar o deslocamento.
Novo comportamento
A partir de Microsoft.Data.Sqlite 10.0, ao escrever um DateTimeOffset valor em uma coluna REAL, Microsoft.Data.Sqlite converterá o valor em UTC antes de fazer as conversões e gravá-lo.
Porquê
O valor escrito estava incorreto, não se alinhando com o comportamento do SQLite, onde os carimbos de data/hora REAL são considerados como UTC.
Atenuações
O código deve ser ajustado em conformidade.
Como último recurso/temporário, você pode reverter para o comportamento anterior definindo Microsoft.Data.Sqlite.Pre10TimeZoneHandling a opção AppContext como true, consulte AppContext para consumidores de biblioteca para obter mais detalhes.
AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);
Usar GetDateTime com um deslocamento agora retorna o valor em UTC
Problema de rastreamento #36195
Comportamento antigo
Anteriormente, ao usar GetDateTime em um carimbo de data/hora textual que tinha um deslocamento (por exemplo, 2014-04-15 10:47:16+02:00), Microsoft.Data.Sqlite retornava o valor com DateTimeKind.Local (mesmo que o deslocamento não fosse local). O tempo foi analisado corretamente levando em conta o deslocamento.
Novo comportamento
A partir de Microsoft.Data.Sqlite 10.0, ao usar GetDateTime em um carimbo de data/hora textual que tenha um deslocamento, Microsoft.Data.Sqlite converterá o valor em UTC e o retornará com DateTimeKind.Utc.
Porquê
Embora a hora tenha sido analisada corretamente, ela dependia do fuso horário local configurado pela máquina, o que poderia levar a resultados inesperados.
Atenuações
O código deve ser ajustado em conformidade.
Como último recurso/temporário, você pode reverter para o comportamento anterior definindo Microsoft.Data.Sqlite.Pre10TimeZoneHandling a opção AppContext como true, consulte AppContext para consumidores de biblioteca para obter mais detalhes.
AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);