Alterações significativas incluídas no EF Core 3.x
As seguintes alterações de API e comportamento têm o potencial de quebrar aplicativos existentes ao atualizá-los para 3.x. As alterações que esperamos que afetem apenas os provedores de banco de dados são documentadas sob alterações nos provedores.
Resumo
Alterações de alto impacto
As consultas LINQ não são mais avaliadas no cliente
Tracking Issue #14935Consulte também a questão #12795
Comportamento antigo
Antes da versão 3.0, quando o EF Core não conseguia converter uma expressão que fazia parte de uma consulta em SQL ou em um parâmetro, ele avaliava automaticamente a expressão no cliente. Por padrão, a avaliação do cliente de expressões potencialmente caras gerou apenas um aviso.
Novo comportamento
A partir da versão 3.0, o EF Core só permite que expressões na projeção de nível superior (a última chamada de Select()
feita na consulta) sejam avaliadas no cliente.
Quando as expressões em qualquer outra parte da consulta não podem ser convertidas em SQL ou em um parâmetro, uma exceção é lançada.
Porquê
A avaliação automática de consultas pelo cliente permite que muitas consultas sejam executadas, mesmo que partes importantes delas não possam ser traduzidas.
Este comportamento pode resultar em comportamentos inesperados e potencialmente prejudiciais que só podem tornar-se evidentes na produção.
Por exemplo, uma condição em uma chamada de Where()
que não pode ser traduzida pode fazer com que todas as linhas da tabela sejam transferidas do servidor de banco de dados e o filtro seja aplicado no cliente.
Essa situação pode facilmente passar despercebida se a tabela contiver apenas algumas linhas durante o desenvolvimento, mas pode ter um impacto significativo quando a aplicação é transferida para a produção, onde a tabela pode conter milhões de linhas.
Os avisos de avaliação do cliente também se mostraram muito fáceis de ignorar durante o desenvolvimento.
Além disso, a avaliação automática do utilizador pode levar a problemas em que a melhoria da tradução de consultas para expressões específicas causou alterações disruptivas não intencionais nas diferentes versões.
Atenuações
Se uma consulta não puder ser totalmente traduzida, reescreva a consulta em um formulário que possa ser traduzido ou use AsEnumerableAsync()
, ToListAsync()
ou similar para trazer explicitamente os dados de volta ao cliente, onde eles podem ser processados usando LINQ-to-Objects.
Alterações de impacto médio
O Entity Framework Core não faz mais parte da estrutura compartilhada do ASP.NET Core
Tracking Issue Announcements#325
Comportamento antigo
Antes do ASP.NET Core 3.0, quando você adicionava uma referência de pacote ao Microsoft.AspNetCore.App
ou Microsoft.AspNetCore.All
, ele incluía o EF Core e alguns dos provedores de dados do EF Core, como o provedor do SQL Server.
Novo comportamento
A partir da versão 3.0, a estrutura compartilhada do ASP.NET Core não inclui o EF Core nem nenhum provedor de dados do EF Core.
Porquê
Antes dessa alteração, obter o EF Core exigia etapas diferentes, dependendo de se o aplicativo era direcionado para ASP.NET Core e SQL Server, ou não. Além disso, a atualização do ASP.NET Core forçou a atualização do EF Core e do provedor do SQL Server, o que nem sempre é desejável.
Com essa alteração, a experiência de obter o EF Core é a mesma em todos os provedores, implementações .NET suportadas e tipos de aplicativos. Agora, os desenvolvedores também podem controlar exatamente quando o EF Core e os provedores de dados do EF Core são atualizados.
Atenuações
Para usar o EF Core em um aplicativo ASP.NET Core 3.0 ou qualquer outro aplicativo suportado, adicione explicitamente uma referência de pacote ao provedor de banco de dados EF Core que seu aplicativo usará.
A ferramenta de linha de comando EF Core, dotnet ef, não faz mais parte do SDK do .NET Core
Comportamento antigo
Antes da versão 3.0, a ferramenta dotnet ef
estava incluída no SDK do .NET Core e estava prontamente disponível para uso a partir da linha de comando de qualquer projeto sem exigir etapas extras.
Novo comportamento
A partir da versão 3.0, o SDK do .NET não inclui a ferramenta dotnet ef
, portanto, antes de usá-la, é necessário instalá-la explicitamente como uma ferramenta local ou global.
Porquê
Essa alteração nos permite distribuir e atualizar dotnet ef
como uma ferramenta regular da CLI do .NET no NuGet, consistente com o fato de que o EF Core 3.0 também é sempre distribuído como um pacote NuGet.
Atenuações
Para poder gerir migrações ou estruturar um DbContext
, instale dotnet-ef
como uma ferramenta global.
dotnet tool install --global dotnet-ef
Pode também obter uma ferramenta local ao restaurar as dependências de um projeto que a declara como dependência de ferramentas, usando um arquivo de manifesto de ferramentas .
Alterações de baixo impacto
FromSql, ExecuteSQL e ExecuteSqlAsync foram renomeados
Questão de Acompanhamento #10996
Importante
ExecuteSqlCommand
e ExecuteSqlCommandAsync
foram preteridos. Em vez disso, use esses métodos.
Comportamento antigo
Antes do EF Core 3.0, esses nomes de método eram sobrecarregados para trabalhar com uma cadeia de caracteres normal ou uma cadeia de caracteres que deveria ser interpolada em SQL e parâmetros.
Novo comportamento
A partir do EF Core 3.0, use FromSqlRaw
, ExecuteSqlRaw
e ExecuteSqlRawAsync
para criar uma consulta parametrizada onde os parâmetros são passados separadamente da cadeia de caracteres de consulta.
Por exemplo:
context.Products.FromSqlRaw(
"SELECT * FROM Products WHERE Name = {0}",
product.Name);
Use FromSqlInterpolated
, ExecuteSqlInterpolated
e ExecuteSqlInterpolatedAsync
para criar uma consulta parametrizada onde os parâmetros são passados como parte de uma cadeia de caracteres de consulta interpolada.
Por exemplo:
context.Products.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Name = {product.Name}");
Observe que ambas as consultas acima produzirão o mesmo SQL parametrizado com os mesmos parâmetros SQL.
Porquê
Sobrecargas de método como essa tornam muito fácil chamar acidentalmente o método de cadeia de caracteres bruta quando a intenção era chamar o método de cadeia de caracteres interpolada, e vice-versa. Isso pode fazer com que as consultas não sejam parametrizadas quando deveriam ter sido.
Atenuações
Alterne para usar os novos nomes de método.
O método FromSql quando usado com o procedimento armazenado não pode ser composto
Comportamento antigo
Antes do EF Core 3.0, o método FromSql tentava detetar se o SQL fornecido pudesse ser composto. Ele fazia a avaliação do cliente quando o SQL não era componível como um procedimento armazenado. A consulta a seguir funcionou executando o procedimento armazenado no servidor e fazendo FirstOrDefault no lado do cliente.
context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").FirstOrDefault();
Novo comportamento
A partir do EF Core 3.0, o EF Core não tentará analisar o SQL. Portanto, se estiver a compor após o FromSqlRaw/FromSqlInterpolated, o EF Core irá compor o SQL gerando uma subconsulta. Portanto, se você estiver usando um procedimento armazenado com composição, obterá uma exceção para sintaxe SQL inválida.
Porquê
O EF Core 3.0 não suporta a avaliação automática do cliente, uma vez que era propenso a erros, conforme explicado aqui.
Atenuações
Se você estiver usando um procedimento armazenado em FromSqlRaw/FromSqlInterpolated, você sabe que ele não pode ser composto, então você pode adicionar AsEnumerable
/AsAsyncEnumerable
logo após a chamada do método FromSql para evitar qualquer composição no lado do servidor.
context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").AsEnumerable().FirstOrDefault();
Os métodos FromSql só podem ser especificados em raízes de consulta
Comportamento antigo
Antes do EF Core 3.0, o método FromSql
podia ser especificado em qualquer lugar na consulta.
Novo comportamento
A partir do EF Core 3.0, os novos métodos FromSqlRaw
e FromSqlInterpolated
(que substituem FromSql
) só podem ser especificados nas raízes de consulta, ou seja, diretamente no DbSet<>
. Tentar especificá-los em qualquer outro lugar resultará em um erro de compilação.
Porquê
Especificar FromSql
em qualquer outro lugar que não em um DbSet
não tinha significado ou valor agregado e poderia causar ambiguidade em certos cenários.
Atenuações
FromSql
invocações devem ser movidas para ficarem diretamente sobre o DbSet
ao qual se aplicam.
As consultas sem rastreamento não executam mais a resolução de identidade
Comportamento antigo
Antes do EF Core 3.0, a mesma instância de entidade era usada para cada ocorrência de uma entidade com um determinado tipo e ID. Isso corresponde ao comportamento das consultas de rastreamento. Por exemplo, esta consulta:
var results = await context.Products.Include(e => e.Category).AsNoTracking().ToListAsync();
retornaria a mesma instância Category
para cada Product
associado à categoria especificada.
Novo comportamento
A partir do EF Core 3.0, instâncias de entidade diferentes serão criadas quando uma entidade com um determinado tipo e ID for encontrada em locais diferentes no gráfico retornado. Por exemplo, a consulta acima agora retornará uma nova instância de Category
para cada Product
mesmo quando dois produtos estiverem associados à mesma categoria.
Porquê
A resolução de identidade (ou seja, determinar que uma entidade tem o mesmo tipo e ID que uma entidade encontrada anteriormente) adiciona desempenho adicional e sobrecarga de memória. Isso geralmente vai contra o motivo pelo qual as consultas sem rastreamento são usadas em primeiro lugar. Além disso, embora a resolução de identidade às vezes possa ser útil, ela não é necessária se as entidades forem serializadas e enviadas para um cliente, o que é comum para consultas sem rastreamento.
Atenuações
Utilize uma consulta de rastreamento se a resolução de identidade for necessária.
Os valores de chave temporários não são mais definidos em instâncias de entidade
Comportamento antigo
Antes do EF Core 3.0, valores temporários eram atribuídos a todas as propriedades de chave que mais tarde teriam um valor real gerado pelo banco de dados. Normalmente, estes valores temporários eram grandes números negativos.
Novo comportamento
A partir da versão 3.0, o EF Core armazena o valor da chave temporária como parte das informações de rastreamento da entidade e deixa a própria propriedade da chave inalterada.
Porquê
Essa alteração foi feita para evitar que valores de chave temporários se tornem erroneamente permanentes quando uma entidade que foi rastreada anteriormente por alguma instância DbContext
é movida para uma instância DbContext
diferente.
Atenuações
Os aplicativos que atribuem valores de chave primária a chaves estrangeiras para formar associações entre entidades podem depender do comportamento antigo se as chaves primárias forem geradas por armazenamento e pertencerem a entidades no estado Added
.
Isto pode ser evitado mediante:
- Não usar chaves geradas pela loja.
- Definir propriedades de navegação para formar relações em vez de definir valores de chave estrangeira.
- Obtenha os valores de chave temporários reais das informações de rastreio da entidade.
Por exemplo,
context.Entry(blog).Property(e => e.Id).CurrentValue
retornará o valor temporário mesmo queblog.Id
em si não tenha sido definido.
DetectChanges honra os valores-chave gerados pela loja
Comportamento antigo
Antes do EF Core 3.0, uma entidade não rastreada encontrada por DetectChanges
era rastreada no estado Added
e inserida como uma nova linha quando SaveChanges
é chamada.
Novo comportamento
A partir do EF Core 3.0, se uma entidade estiver usando valores de chave gerados e algum valor de chave for definido, a entidade será rastreada no estado Modified
.
Isso significa que se assume a existência de uma linha para a entidade, que será atualizada quando SaveChanges
for chamado.
Se o valor da chave não estiver definido, ou se o tipo de entidade não estiver usando chaves geradas, a nova entidade ainda será rastreada Added
como nas versões anteriores.
Porquê
Essa alteração foi feita para tornar mais fácil e consistente trabalhar com gráficos de entidade desconectados ao usar chaves geradas pela loja.
Atenuações
Essa alteração pode quebrar um aplicativo se um tipo de entidade estiver configurado para usar chaves geradas, mas os valores de chave forem explicitamente definidos para novas instâncias. A correção é configurar explicitamente as propriedades da chave para não usar valores gerados. Por exemplo, com a API fluente:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.ValueGeneratedNever();
Ou com anotações de dados:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
As exclusões em cascata agora acontecem imediatamente por padrão
Problema de rastreamento #10114
Comportamento antigo
Antes da versão 3.0, o EF Core aplicava ações em cascata (eliminando entidades dependentes quando uma entidade principal obrigatória era excluída ou quando a relação com uma entidade principal obrigatória era cortada) somente quando SaveChanges era chamado.
Novo comportamento
A partir da versão 3.0, o EF Core aplica ações em cascata assim que a condição de acionamento é detetada.
Por exemplo, chamar context.Remove()
para eliminar uma entidade principal resultará em que todas as entidades dependentes obrigatórias relacionadas e rastreadas sejam também definidas para Deleted
imediatamente.
Porquê
Essa alteração foi feita para melhorar a experiência em cenários de vinculação e auditoria de dados, onde é importante entender quais entidades serão excluídas antes queSaveChanges
seja chamada.
Atenuações
O comportamento anterior pode ser restaurado através de configurações no context.ChangeTracker
.
Por exemplo:
context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
O carregamento antecipado de entidades relacionadas agora acontece numa única consulta
Problema de rastreamento #18022
Comportamento antigo
Antes da versão 3.0, o carregamento ávido de navegações de coleta por meio de operadores de Include
fazia com que várias consultas fossem geradas no banco de dados relacional, uma para cada tipo de entidade relacionada.
Novo comportamento
A partir da versão 3.0, o EF Core gera uma única consulta com JOINs em bancos de dados relacionais.
Porquê
A emissão de várias consultas para implementar uma única consulta LINQ causou vários problemas, incluindo desempenho negativo, pois várias viagens de ida e volta do banco de dados eram necessárias, e problemas de coerência de dados, pois cada consulta podia observar um estado diferente do banco de dados.
Atenuações
Embora tecnicamente essa não seja uma alteração de rutura, ela pode ter um efeito considerável no desempenho do aplicativo quando uma única consulta contém um grande número de operadores de Include
em navegações de coleção.
Consulte este comentário para obter mais informações e para reescrever consultas de uma forma mais eficiente.
**
DeleteBehavior.Restrict tem semântica mais limpa
Comportamento antigo
Antes da versão 3.0, DeleteBehavior.Restrict
criava chaves estrangeiras na base de dados com semântica Restrict
, mas também alterava o ajuste interno de uma maneira pouco evidente.
Novo comportamento
A partir da versão 3.0, DeleteBehavior.Restrict
garante que as chaves estrangeiras sejam criadas com semântica Restrict
, ou seja, sem cascatas; gerar erro em caso de violação de restrição - sem também afetar o ajuste interno do EF.
Porquê
Esta alteração foi feita para melhorar a experiência de utilização do DeleteBehavior
de uma forma intuitiva, sem efeitos secundários inesperados.
Atenuações
O comportamento anterior pode ser restaurado usando DeleteBehavior.ClientNoAction
.
Os tipos de consulta são consolidados com tipos de entidade
Comportamento antigo
Antes do EF Core 3.0, tipos de consulta eram um meio de consultar dados que não definem uma chave primária de forma estruturada. Ou seja, um tipo de consulta foi utilizado para mapear tipos de entidade sem chaves (mais provavelmente a partir de uma vista, mas possivelmente de uma tabela), enquanto um tipo de entidade regular foi utilizado quando uma chave estava disponível (mais provavelmente de uma tabela, mas possivelmente de uma vista).
Novo comportamento
Um tipo de consulta agora se torna apenas um tipo de entidade sem uma chave primária. Os tipos de entidade sem chave têm a mesma funcionalidade que os tipos de consulta em versões anteriores.
Porquê
Essa alteração foi feita para reduzir a confusão em torno da finalidade dos tipos de consulta. Especificamente, são tipos de entidades sem chave e são inerentemente de leitura apenas, mas não devem ser usados apenas pelo facto de um tipo de entidade precisar ser de leitura apenas. Da mesma forma, eles geralmente são mapeados para modos de exibição, mas isso ocorre apenas porque os modos de exibição geralmente não definem chaves.
Atenuações
As seguintes partes da API estão obsoletas:
-
ModelBuilder.Query<>()
- Em vez disso,ModelBuilder.Entity<>().HasNoKey()
precisa ser chamado para marcar um tipo de entidade como não tendo chaves. Isso ainda não seria configurado por convenção para evitar erros de configuração quando uma chave primária é esperada, mas não corresponde à convenção. -
DbQuery<>
- Em vez disso,DbSet<>
deve ser usado. -
DbContext.Query<>()
- Em vez disso,DbContext.Set<>()
deve ser usado. -
IQueryTypeConfiguration<TQuery>
- Em vez disso,IEntityTypeConfiguration<TEntity>
deve ser usado.
Observação
Devido a um problema no 3.x ao consultar entidades sem chave que têm todas as propriedades definidas como null
, será retornado um null
em vez de uma entidade. Se este problema for aplicável ao seu cenário, também adicione lógica para lidar com null
nos resultados.
A API de configuração para relações de tipo de propriedade foi alterada
Problema de rastreamento #12444Problema de rastreamento #9148Problema de rastreamento #14153
Comportamento antigo
Antes do EF Core 3.0, a configuração do relacionamento de propriedade era realizada diretamente após a chamada OwnsOne
ou OwnsMany
.
Novo comportamento
A partir do EF Core 3.0, há agora uma API fluente para configurar uma propriedade de navegação para o proprietário usando WithOwner()
.
Por exemplo:
modelBuilder.Entity<Order>.OwnsOne(e => e.Details).WithOwner(e => e.Order);
A configuração relacionada à relação entre proprietário e possuído agora deve ser encadeada após WithOwner()
, semelhante a outras relações configuradas.
Enquanto a configuração para o tipo possuído em si ainda estaria encadeada após OwnsOne()/OwnsMany()
.
Por exemplo:
modelBuilder.Entity<Order>.OwnsOne(e => e.Details, eb =>
{
eb.WithOwner()
.HasForeignKey(e => e.AlternateId)
.HasConstraintName("FK_OrderDetails");
eb.ToTable("OrderDetails");
eb.HasKey(e => e.AlternateId);
eb.HasIndex(e => e.Id);
eb.HasOne(e => e.Customer).WithOne();
eb.HasData(
new OrderDetails
{
AlternateId = 1,
Id = -1
});
});
Além disso, chamar Entity()
, HasOne()
ou Set()
com um destino de tipo próprio agora lançará uma exceção.
Porquê
Essa alteração foi feita para criar uma separação mais clara entre a configuração do próprio tipo possuído e a relação com e desse tipo possuído.
Isto, por sua vez, elimina a ambiguidade e a confusão em torno de métodos como HasForeignKey
.
Atenuações
Altere a configuração das relações de tipo de propriedade para usar a nova superfície da API, conforme mostrado no exemplo acima.
As entidades dependentes que partilham a tabela com a entidade principal são opcionais agora.
Comportamento antigo
Considere o seguinte modelo:
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public OrderDetails Details { get; set; }
}
public class OrderDetails
{
public int Id { get; set; }
public string ShippingAddress { get; set; }
}
Antes do EF Core 3.0, se OrderDetails
fosse propriedade de Order
ou fosse explicitamente mapeado na mesma tabela, uma instância OrderDetails
sempre era necessária ao adicionar um novo Order
.
Novo comportamento
A partir da versão 3.0, o EF Core permite adicionar uma Order
sem uma OrderDetails
e mapeia todas as propriedades OrderDetails
para colunas anuláveis, exceto a chave primária.
Ao consultar o EF Core define OrderDetails
para null
se alguma de suas propriedades necessárias não tem um valor ou se não tem propriedades necessárias além da chave primária e todas as propriedades são null
.
Atenuações
Se o seu modelo tiver um compartilhamento de tabela dependente com todas as colunas opcionais, mas a navegação apontando para ele não deve ser null
então o aplicativo deve ser modificado para lidar com casos quando a navegação estiver null
. Se isso não for possível, uma propriedade necessária deve ser adicionada ao tipo de entidade ou pelo menos uma propriedade deve ter um valor nãonull
atribuído a ela.
Todas as entidades que partilham uma tabela com uma coluna de marcador de concorrência têm de mapeá-la para uma propriedade.
Comportamento antigo
Considere o seguinte modelo:
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public byte[] Version { get; set; }
public OrderDetails Details { get; set; }
}
public class OrderDetails
{
public int Id { get; set; }
public string ShippingAddress { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
}
Antes do EF Core 3.0, se OrderDetails
pertencer a Order
ou for explicitamente mapeado para a mesma tabela, atualizar apenas OrderDetails
não atualizará o valor de Version
no cliente e a próxima atualização irá falhar.
Novo comportamento
A partir da versão 3.0, EF Core propaga o novo valor Version
para Order
se tiver OrderDetails
. Caso contrário, uma exceção será lançada durante a validação do modelo.
Porquê
Essa alteração foi feita para evitar um valor de token de simultaneidade obsoleto quando apenas uma das entidades mapeadas para a mesma tabela é atualizada.
Atenuações
Todas as entidades que compartilham a tabela devem incluir uma propriedade que esteja mapeada com a coluna do token de simultaneidade. É possível criar um em estado de sombra:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrderDetails>()
.Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
}
As entidades de propriedade não podem ser consultadas sem que o proprietário use uma consulta de acompanhamento
Comportamento antigo
Antes do EF Core 3.0, as entidades proprietárias podiam ser consultadas como qualquer outra navegação.
context.People.Select(p => p.Address);
Novo comportamento
A partir da versão 3.0, o EF Core irá lançar uma exceção se uma consulta de rastreamento projetar uma entidade subordinada sem o seu proprietário.
Porquê
As entidades possuídas não podem ser manipuladas sem o seu proprietário, pelo que, na grande maioria dos casos, consultá-las desta forma é um erro.
Atenuações
Se a entidade de propriedade deve ser rastreada para ser modificada de alguma forma mais tarde, então o proprietário deve ser incluído na consulta.
Caso contrário, adicione uma chamada AsNoTracking()
:
context.People.Select(p => p.Address).AsNoTracking();
As propriedades herdadas de tipos não mapeados agora são mapeadas para uma única coluna para todos os tipos derivados
Questão de Rastreamento #13998
Comportamento antigo
Considere o seguinte modelo:
public abstract class EntityBase
{
public int Id { get; set; }
}
public abstract class OrderBase : EntityBase
{
public int ShippingAddress { get; set; }
}
public class BulkOrder : OrderBase
{
}
public class Order : OrderBase
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<OrderBase>();
modelBuilder.Entity<EntityBase>();
modelBuilder.Entity<BulkOrder>();
modelBuilder.Entity<Order>();
}
Antes do EF Core 3.0, a propriedade ShippingAddress
era mapeada para colunas separadas para BulkOrder
e Order
por padrão.
Novo comportamento
A partir da versão 3.0, o EF Core cria apenas uma coluna para ShippingAddress
.
Porquê
O comportamento antigo foi inesperado.
Atenuações
A propriedade ainda pode ser explicitamente mapeada para uma coluna separada nos tipos derivados.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<OrderBase>();
modelBuilder.Entity<EntityBase>();
modelBuilder.Entity<BulkOrder>()
.Property(o => o.ShippingAddress).HasColumnName("BulkShippingAddress");
modelBuilder.Entity<Order>()
.Property(o => o.ShippingAddress).HasColumnName("ShippingAddress");
}
A convenção de propriedade de chave estrangeira já não tem o mesmo nome que a propriedade principal
Comportamento antigo
Considere o seguinte modelo:
public class Customer
{
public int CustomerId { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
Antes do EF Core 3.0, a propriedade CustomerId
era utilizada como chave estrangeira por convenção.
No entanto, se Order
é um tipo possuído, então isto também tornaria CustomerId
a chave primária, o que não corresponde normalmente à expectativa.
Novo comportamento
A partir da versão 3.0, o EF Core não tenta usar propriedades para chaves estrangeiras por convenção se elas tiverem o mesmo nome da propriedade principal. O nome do tipo principal concatenado com o nome da propriedade principal, e o nome da navegação concatenado com o nome da propriedade principal, ainda correspondem aos padrões. Por exemplo:
public class Customer
{
public int Id { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
public class Customer
{
public int Id { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int BuyerId { get; set; }
public Customer Buyer { get; set; }
}
Porquê
Essa alteração foi feita para evitar a definição errada de uma propriedade de chave primária no tipo possuído.
Atenuações
Se a propriedade foi destinada a ser a chave estrangeira e, portanto, parte da chave primária, configure-a explicitamente como tal.
A conexão do banco de dados agora é fechada se não for mais usada antes que o TransactionScope tenha sido concluído
Comportamento antigo
Antes do EF Core 3.0, se o contexto abrir a conexão dentro de um TransactionScope
, a conexão permanecerá aberta enquanto o TransactionScope
atual estiver ativo.
using (new TransactionScope())
{
using (AdventureWorks context = new AdventureWorks())
{
context.ProductCategories.Add(new ProductCategory());
await context.SaveChangesAsync();
// Old behavior: Connection is still open at this point
var categories = await context.ProductCategories().ToListAsync();
}
}
Novo comportamento
A partir da versão 3.0, o EF Core fecha a conexão assim que termina de usá-la.
Porquê
Esta alteração permite utilizar vários contextos no mesmo TransactionScope
. O novo comportamento também corresponde ao EF6.
Atenuações
Se a conexão precisar permanecer aberta, a chamada explícita para OpenConnection()
garantirá que o EF Core não a feche prematuramente:
using (new TransactionScope())
{
using (AdventureWorks context = new AdventureWorks())
{
await context.Database.OpenConnectionAsync();
context.ProductCategories.Add(new ProductCategory());
await context.SaveChangesAsync();
var categories = await context.ProductCategories().ToListAsync();
await context.Database.CloseConnectionAsync();
}
}
Cada propriedade utiliza geração independente de chaves inteiras na memória
Comportamento antigo
Antes do EF Core 3.0, um gerador de valor compartilhado era usado para todas as propriedades de chave inteira na memória.
Novo comportamento
A partir do EF Core 3.0, cada propriedade de chave inteira obtém seu próprio gerador de valor ao usar o banco de dados na memória. Além disso, se o banco de dados for excluído, a geração de chaves será redefinida para todas as tabelas.
Porquê
Essa alteração foi feita para alinhar a geração de chaves na memória mais de perto com a geração de chaves de banco de dados real e para melhorar a capacidade de isolar testes uns dos outros ao usar o banco de dados na memória.
Atenuações
Isso pode quebrar um aplicativo que depende de valores de chave na memória específicos a serem definidos. Em vez disso, considere não confiar em valores de chave específicos ou atualizar para corresponder ao novo comportamento.
Os campos de suporte são usados por padrão
Problema de Rastreamento #12430
Comportamento antigo
Antes da versão 3.0, mesmo que o campo de apoio de uma propriedade fosse conhecido, o EF Core ainda lia e escrevia, por padrão, o valor da propriedade usando os métodos getter e setter da propriedade. A exceção a isso era a execução da consulta, onde o campo de suporte seria definido diretamente, se conhecido.
Novo comportamento
A partir do EF Core 3.0, se o campo de suporte de uma propriedade for conhecido, o EF Core sempre lerá e gravará essa propriedade usando o campo de suporte. Isso pode causar uma quebra de aplicativo se o aplicativo estiver confiando em comportamento adicional codificado nos métodos getter ou setter.
Porquê
Essa alteração foi feita para evitar que o EF Core acionasse erroneamente a lógica de negócios por padrão ao executar operações de banco de dados envolvendo as entidades.
Atenuações
O comportamento pré-3.0 pode ser restaurado através da configuração do modo de acesso à propriedade no ModelBuilder
.
Por exemplo:
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);
Lance se vários campos de suporte compatíveis forem encontrados
Comportamento antigo
Antes do EF Core 3.0, se vários campos correspondessem às regras para encontrar o campo de suporte de uma propriedade, um campo seria escolhido com base em alguma ordem de precedência. Isso pode fazer com que o campo errado seja usado em casos ambíguos.
Novo comportamento
A partir do EF Core 3.0, se vários campos forem associados à mesma propriedade, será lançada uma exceção.
Porquê
Esta alteração foi feita para evitar o uso silencioso de um campo sobre outro quando apenas um pode estar correto.
Atenuações
As propriedades com campos de suporte ambíguos devem ter o campo a ser usado especificado explicitamente. Por exemplo, usando a API fluente:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.HasField("_id");
Os nomes de campo apenas de propriedade devem corresponder ao nome do campo
Comportamento antigo
Antes do EF Core 3.0, uma propriedade podia ser especificada por um valor de cadeia de caracteres e, se nenhuma propriedade com esse nome fosse encontrada no tipo .NET, o EF Core tentaria correspondê-la a um campo usando regras de convenção.
private class Blog
{
private int _id;
public string Name { get; set; }
}
modelBuilder
.Entity<Blog>()
.Property("Id");
Novo comportamento
A partir do EF Core 3.0, uma propriedade exclusiva de campo deve corresponder exatamente ao nome do campo.
modelBuilder
.Entity<Blog>()
.Property("_id");
Porquê
Essa alteração foi feita para evitar o uso do mesmo campo para duas propriedades nomeadas de forma semelhante, ela também torna as regras de correspondência para propriedades somente de campo as mesmas que para propriedades mapeadas para propriedades CLR.
Atenuações
As propriedades exclusivas de campo devem ter o mesmo nome que o campo ao qual estão mapeadas. Em uma versão futura do EF Core após a versão 3.0, planejamos reativar a configuração explícita de um nome de campo diferente do nome da propriedade (consulte o problema #15307):
modelBuilder
.Entity<Blog>()
.Property("Id")
.HasField("_id");
AddDbContext/AddDbContextPool não chama mais AddLogging e AddMemoryCache
Comportamento antigo
Antes do EF Core 3.0, chamar AddDbContext
ou AddDbContextPool
também registava serviços de registo de logs e cache de memória com DI, através de chamadas para AddLogging e AddMemoryCache.
Novo comportamento
A partir do EF Core 3.0, AddDbContext
e AddDbContextPool
não registrarão mais esses serviços com a injeção de dependência (DI).
Porquê
O EF Core 3.0 não requer que esses serviços estejam no contêiner DI do aplicativo. No entanto, se ILoggerFactory
estiver registrado no contêiner DI do aplicativo, ele ainda será usado pelo EF Core.
Atenuações
Se seu aplicativo precisar desses serviços, registre-os explicitamente no contêiner DI usando AddLogging ou AddMemoryCache.
AddEntityFramework* adiciona IMemoryCache com um limite de tamanho
Comportamento antigo
Antes do EF Core 3.0, chamar AddEntityFramework*
métodos também registrava serviços de cache de memória com DI sem um limite de tamanho.
Novo comportamento
A partir do EF Core 3.0, AddEntityFramework*
registrará um serviço IMemoryCache com um limite de tamanho. Se quaisquer outros serviços adicionados posteriormente dependerem do IMemoryCache, eles podem atingir rapidamente o limite padrão, causando exceções ou desempenho degradado.
Porquê
Usar IMemoryCache sem um limite pode resultar em uso descontrolado de memória se houver um bug na lógica de cache de consulta ou se as consultas forem geradas dinamicamente. Ter um limite padrão atenua um possível ataque DoS.
Atenuações
Na maioria dos casos, ligar para AddEntityFramework*
não é necessário se AddDbContext
ou AddDbContextPool
também for chamado. Portanto, a melhor mitigação é remover a chamada AddEntityFramework*
.
Se seu aplicativo precisar desses serviços, registre uma implementação IMemoryCache explicitamente com o contêiner DI de antemão usando AddMemoryCache.
DbContext.Entry agora executa um DetectChanges local
Comportamento antigo
Antes do EF Core 3.0, chamar DbContext.Entry
fazia com que alterações fossem detetadas para todas as entidades rastreadas.
Isso garantiu que o estado exposto no EntityEntry
estivesse up-todata.
Novo comportamento
A partir do EF Core 3.0, chamar DbContext.Entry
agora só tentará detetar alterações na entidade específica e em quaisquer entidades principais controladas relacionadas a ela.
Isso significa que as alterações em outros lugares podem não ter sido detetadas chamando esse método, o que pode ter implicações no estado do aplicativo.
Observe que, se ChangeTracker.AutoDetectChangesEnabled
estiver definido como false
até mesmo essa deteção de alteração local será desabilitada.
Outros métodos que causam deteção de alterações - por exemplo, ChangeTracker.Entries
e SaveChanges
- ainda causam uma DetectChanges
completa de todas as entidades rastreadas.
Porquê
Essa alteração foi feita para melhorar o desempenho padrão do uso do context.Entry
.
Atenuações
Chame ChangeTracker.DetectChanges()
explicitamente antes de chamar Entry
para garantir o comportamento pré-3.0.
As chaves String e array de bytes não são geradas pelo cliente por padrão
Comportamento antigo
Antes do EF Core 3.0, string
e byte[]
propriedades de chave podiam ser usadas sem definir explicitamente um valor não nulo.
Nesse caso, o valor da chave seria gerado no cliente sob a forma de um GUID, e depois serializado em bytes para byte[]
.
Novo comportamento
A partir do EF Core 3.0, será lançada uma exceção indicando que nenhum valor de chave foi definido.
Porquê
Essa alteração foi feita porque os valores de string
/byte[]
gerados pelo cliente geralmente não são úteis, e o comportamento padrão tornou difícil raciocinar sobre os valores de chave gerados de uma maneira comum.
Atenuações
O comportamento pré-3.0 pode ser obtido especificando explicitamente que as propriedades de chave devem usar valores gerados se nenhum outro valor não nulo for definido. Por exemplo, com a API fluente:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.ValueGeneratedOnAdd();
Ou com anotações de dados:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
ILoggerFactory é agora um serviço com escopo
Comportamento antigo
Antes do EF Core 3.0, ILoggerFactory
era registado como um serviço singleton.
Novo comportamento
A partir do EF Core 3.0, ILoggerFactory
agora está registrado como escopo.
Porquê
Esta alteração foi feita para permitir a associação de um registador com uma instância DbContext
, o que permite outras funcionalidades e elimina alguns casos de comportamento patológico, como uma explosão de prestadores de serviços internos.
Atenuações
Essa alteração não deve afetar o código do aplicativo, a menos que ele esteja registrando e usando serviços personalizados no provedor de serviços interno do EF Core.
Isso não é comum.
Nesses casos, a maioria das coisas ainda funcionará, mas qualquer serviço singleton que dependia de ILoggerFactory
precisará ser alterado para obter o ILoggerFactory
de forma diferente.
Se você se deparar com situações como esta, registre um problema no do rastreador de problemas do EF Core GitHub para nos informar como você está usando ILoggerFactory
para que possamos entender melhor como não quebrar isso novamente no futuro.
Os proxies de carregamento lento não assumem mais que as propriedades de navegação estão totalmente carregadas
Problema de rastreamento #12780
Comportamento antigo
Antes do EF Core 3.0, uma vez que um DbContext
era descartado, não havia como saber se uma determinada propriedade de navegação em uma entidade obtida a partir desse contexto estava totalmente carregada ou não.
Em vez disso, os proxies assumem que uma navegação de referência é carregada se tiver um valor não nulo e que uma navegação de coleção é carregada se não estiver vazia.
Nestes casos, tentar fazer um carregamento lento seria uma no-op.
Novo comportamento
A partir do EF Core 3.0, os proxies controlam se uma propriedade de navegação é carregada ou não. Isso significa que tentar acessar uma propriedade de navegação que é carregada depois que o contexto foi descartado sempre será um no-op, mesmo quando a navegação carregada estiver vazia ou nula. Por outro lado, tentar acessar uma propriedade de navegação que não está carregada lançará uma exceção se o contexto for descartado, mesmo que a propriedade de navegação seja uma coleção não vazia. Se essa situação surgir, isso significa que o código do aplicativo está tentando usar o carregamento lento em um momento inválido, e o aplicativo deve ser alterado para não fazer isso.
Porquê
Essa alteração foi feita para tornar o comportamento consistente e correto ao tentar carregar preguiçosamente em uma instância DbContext
descartada.
Atenuações
Atualize o código do aplicativo para não tentar carregar com lentidão com um contexto descartado ou configure isso para ser um no-op conforme descrito na mensagem de exceção.
A criação excessiva de prestadores de serviços internos é agora um erro por defeito
Problema de rastreamento #10236
Comportamento antigo
** Antes do EF Core 3.0, um aviso seria registado quando um aplicativo criava um número patológico de fornecedores de serviços internos.
Novo comportamento
A partir do EF Core 3.0, esse aviso agora é considerado um erro e uma exceção é lançada.
Porquê
Esta alteração foi feita para melhorar o código da aplicação, expondo este caso patológico de forma mais explícita.
Atenuações
A causa mais apropriada de ação ao encontrar esse erro é entender a causa raiz e parar de criar tantos provedores de serviços internos.
No entanto, o erro pode ser convertido novamente em um aviso (ou ignorado) através da configuração no DbContextOptionsBuilder
.
Por exemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.ConfigureWarnings(w => w.Log(CoreEventId.ManyServiceProvidersCreatedWarning));
}
Novo comportamento para HasOne/HasMany quando chamado com uma única string
Questão de Acompanhamento #9171
Comportamento antigo
Antes do EF Core 3.0, a chamada de código HasOne
ou HasMany
com uma única cadeia de caracteres era interpretada de forma confusa.
Por exemplo:
modelBuilder.Entity<Samurai>().HasOne("Entrance").WithOne();
O código parece estar relacionado Samurai
a algum outro tipo de entidade usando a propriedade Entrance
navigation, que pode ser privada.
Na realidade, esse código tenta criar uma relação com algum tipo de entidade chamada Entrance
sem nenhuma propriedade de navegação.
Novo comportamento
Começando com o EF Core 3.0, o código acima agora faz o que parecia que deveria ter feito antes.
Porquê
O comportamento antigo era muito confuso, especialmente ao ler o código de configuração e procurar erros.
Atenuações
Isso quebrará apenas os aplicativos que estão configurando explicitamente relacionamentos usando cadeias de caracteres para nomes de tipo e sem especificar a propriedade de navegação explicitamente.
Isso não é comum.
O comportamento anterior pode ser obtido através da passagem explícita de null
para o nome da propriedade de navegação.
Por exemplo:
modelBuilder.Entity<Samurai>().HasOne("Some.Entity.Type.Name", null).WithOne();
O tipo de retorno para vários métodos assíncronos foi alterado de Task para ValueTask
Rastreamento de Problema #15184
Comportamento antigo
Os seguintes métodos assíncronos retornaram anteriormente um Task<T>
:
DbContext.FindAsync()
DbSet.FindAsync()
DbContext.AddAsync()
DbSet.AddAsync()
-
ValueGenerator.NextValueAsync()
(e classes derivadas)
Novo comportamento
Os métodos mencionados acima agora retornam um ValueTask<T>
sobre o mesmo T
que antes.
Porquê
Essa alteração reduz o número de alocações de heap incorridas ao invocar esses métodos, melhorando o desempenho geral.
Atenuações
Os aplicativos que simplesmente aguardam as APIs acima só precisam ser recompilados - nenhuma alteração de origem é necessária.
Um uso mais complexo (por exemplo, passar o Task
retornado para Task.WhenAny()
) normalmente requer que o ValueTask<T>
retornado seja convertido em um Task<T>
chamando AsTask()
nele.
Note-se que isto anula a redução de alocação que esta alteração traz.
A anotação Relational:TypeMapping agora é apenas TypeMapping
Comportamento antigo
O nome da anotação para anotações de mapeamento de tipo era "Relational:TypeMapping".
Novo comportamento
O nome da anotação para anotações de mapeamento de tipo agora é "TypeMapping".
Porquê
Os mapeamentos de tipo agora são usados para mais do que apenas provedores de banco de dados relacional.
Atenuações
Isso só interromperá os aplicativos que acessam o mapeamento de tipo diretamente como uma anotação, o que não é comum. A ação mais apropriada a ser corrigida é usar a superfície da API para acessar mapeamentos de tipo em vez de usar a anotação diretamente.
ToTable em um tipo derivado lança uma exceção
Comportamento antigo
Antes do EF Core 3.0, o ToTable()
chamado num tipo derivado seria desconsiderado, pois a única estratégia de mapeamento de herança era TPH, no qual isso não é aplicável.
Novo comportamento
A partir do EF Core 3.0 e em preparação para adicionar suporte a TPT e TPC numa versão posterior, a chamada de ToTable()
em um tipo derivado agora gerará uma exceção para evitar uma alteração de mapeamento inesperada no futuro.
Porquê
Atualmente, não é válido mapear um tipo derivado para uma tabela diferente. Esta mudança evita ruturas no futuro, quando se torna uma coisa válida a fazer.
Atenuações
Remova todas as tentativas de mapear tipos derivados para outras tabelas.
ForSqlServerHasIndex substituído por HasIndex
Comportamento antigo
Antes do EF Core 3.0, ForSqlServerHasIndex().ForSqlServerInclude()
fornecia uma maneira de configurar colunas usadas com INCLUDE
.
Novo comportamento
A partir do EF Core 3.0, o uso de Include
em um índice agora é suportado no nível relacional.
Utilize HasIndex().ForSqlServerInclude()
.
Porquê
Essa alteração foi feita para consolidar a API para índices com Include
em um só lugar para todos os provedores de banco de dados.
Atenuações
Use a nova API, como mostrado acima.
Alterações na API de metadados
Novo comportamento
As seguintes propriedades foram convertidas em métodos de extensão:
-
IEntityType.QueryFilter
->GetQueryFilter()
-
IEntityType.DefiningQuery
->GetDefiningQuery()
-
IProperty.IsShadowProperty
->IsShadowProperty()
-
IProperty.BeforeSaveBehavior
->GetBeforeSaveBehavior()
-
IProperty.AfterSaveBehavior
->GetAfterSaveBehavior()
Porquê
Esta alteração simplifica a implementação das interfaces acima mencionadas.
Atenuações
Use os novos métodos de extensão.
Alterações na API de metadados específicas do provedor
Novo comportamento
Os métodos de extensão específicos do provedor serão simplificados.
-
IProperty.Relational().ColumnName
->IProperty.GetColumnName()
-
IEntityType.SqlServer().IsMemoryOptimized
->IEntityType.IsMemoryOptimized()
-
PropertyBuilder.UseSqlServerIdentityColumn()
->PropertyBuilder.UseIdentityColumn()
Porquê
Esta alteração simplifica a implementação dos métodos de extensão acima mencionados.
Atenuações
Use os novos métodos de extensão.
O EF Core não envia mais pragma para imposição de SQLite FK
Comportamento antigo
Antes do EF Core 3.0, o EF Core enviava PRAGMA foreign_keys = 1
quando uma conexão com o SQLite é aberta.
Novo comportamento
A partir do EF Core 3.0, o EF Core não envia mais PRAGMA foreign_keys = 1
quando uma conexão com o SQLite é aberta.
Porquê
Essa alteração foi feita porque o EF Core usa SQLitePCLRaw.bundle_e_sqlite3
por padrão, o que, por sua vez, significa que a imposição de FK está ativada por padrão e não precisa ser explicitamente habilitada cada vez que uma conexão é aberta.
Atenuações
As chaves estrangeiras são habilitadas por padrão no SQLitePCLRaw.bundle_e_sqlite3, que é usado por padrão para o EF Core.
Para outros casos, as chaves estrangeiras podem ser habilitadas especificando Foreign Keys=True
na cadeia de conexão.
Microsoft.EntityFrameworkCore.Sqlite agora depende de SQLitePCLRaw.bundle_e_sqlite3
Comportamento antigo
Antes do EF Core 3.0, o EF Core usava SQLitePCLRaw.bundle_green
.
Novo comportamento
A partir do EF Core 3.0, o EF Core usa SQLitePCLRaw.bundle_e_sqlite3
.
Porquê
Esta alteração foi feita para que a versão do SQLite usada no iOS seja consistente com outras plataformas.
Atenuações
Para usar a versão nativa do SQLite no iOS, configure Microsoft.Data.Sqlite
para usar um pacote de SQLitePCLRaw
diferente.
Os valores Guid agora são armazenados como TEXT no SQLite
Comportamento antigo
Os valores Guid foram previamente armazenados como valores BLOB no SQLite.
Novo comportamento
Os valores Guid agora são armazenados como TEXT.
Porquê
O formato binário do Guids não é padronizado. Armazenar os valores como TEXT torna o banco de dados mais compatível com outras tecnologias.
Atenuações
Você pode migrar bancos de dados existentes para o novo formato executando SQL da seguinte forma.
UPDATE MyTable
SET GuidColumn = hex(substr(GuidColumn, 4, 1)) ||
hex(substr(GuidColumn, 3, 1)) ||
hex(substr(GuidColumn, 2, 1)) ||
hex(substr(GuidColumn, 1, 1)) || '-' ||
hex(substr(GuidColumn, 6, 1)) ||
hex(substr(GuidColumn, 5, 1)) || '-' ||
hex(substr(GuidColumn, 8, 1)) ||
hex(substr(GuidColumn, 7, 1)) || '-' ||
hex(substr(GuidColumn, 9, 2)) || '-' ||
hex(substr(GuidColumn, 11, 6))
WHERE typeof(GuidColumn) == 'blob';
No EF Core, você também pode continuar usando o comportamento anterior configurando um conversor de valor nessas propriedades.
modelBuilder
.Entity<MyEntity>()
.Property(e => e.GuidProperty)
.HasConversion(
g => g.ToByteArray(),
b => new Guid(b));
Microsoft.Data.Sqlite permanece capaz de efetuar a leitura de valores Guid a partir de ambas as colunas BLOB e TEXT; no entanto, como o formato padrão para parâmetros e constantes foi alterado, provavelmente será necessário tomar medidas para a maioria dos cenários que envolvem Guids.
Os valores Char agora são armazenados como TEXT no SQLite
Problema de rastreamento #15020
Comportamento antigo
Os valores Char foram previamente armazenados como valores INTEGER no SQLite. Por exemplo, um valor char de A foi armazenado como o valor inteiro 65.
Novo comportamento
Os valores Char agora são armazenados como TEXT.
Porquê
Armazenar os valores como TEXTO é mais natural e torna o banco de dados mais compatível com outras tecnologias.
Atenuações
Você pode migrar bancos de dados existentes para o novo formato executando SQL da seguinte forma.
UPDATE MyTable
SET CharColumn = char(CharColumn)
WHERE typeof(CharColumn) = 'integer';
No EF Core, você também pode continuar usando o comportamento anterior configurando um conversor de valor nessas propriedades.
modelBuilder
.Entity<MyEntity>()
.Property(e => e.CharProperty)
.HasConversion(
c => (long)c,
i => (char)i);
Microsoft.Data.Sqlite também permanece capaz de ler valores de caracteres das colunas INTEGER e TEXT, portanto, determinados cenários podem não exigir nenhuma ação.
Os IDs de migração agora são gerados usando o calendário da cultura invariante
Comportamento antigo
Os IDs de migração foram gerados inadvertidamente usando o calendário da cultura atual.
Novo comportamento
Os IDs de migração agora são sempre gerados usando o calendário da cultura invariante (gregoriano).
Porquê
A ordem das migrações é importante ao atualizar o banco de dados ou resolver conflitos de mesclagem. Usar o calendário invariante evita problemas de ordenação que podem resultar de membros da equipe com calendários do sistema diferentes.
Atenuações
Esta mudança afeta qualquer pessoa que use um calendário não-gregoriano onde o ano é maior do que o calendário gregoriano (como o calendário budista tailandês). As IDs de migração existentes precisarão ser atualizadas para que novas migrações sejam ordenadas após as migrações existentes.
A ID de migração pode ser encontrada no atributo Migration nos arquivos de designer das migrações.
[DbContext(typeof(MyDbContext))]
-[Migration("25620318122820_MyMigration")]
+[Migration("20190318122820_MyMigration")]
partial class MyMigration
{
A tabela Histórico de migrações também precisa ser atualizada.
UPDATE __EFMigrationsHistory
SET MigrationId = CONCAT(LEFT(MigrationId, 4) - 543, SUBSTRING(MigrationId, 4, 150))
UseRowNumberForPaging foi removido
Questão de Acompanhamento #16400
Comportamento antigo
Antes do EF Core 3.0, UseRowNumberForPaging
podia ser usado para gerar SQL para paginação compatível com o SQL Server 2008.
Novo comportamento
A partir do EF Core 3.0, o EF só gerará SQL para paginação que só seja compatível com versões posteriores do SQL Server.
Porquê
Estamos fazendo essa alteração porque SQL Server 2008 não é mais um produto com suporte e atualizar esse recurso para trabalhar com as alterações de consulta feitas no EF Core 3.0 é um trabalho significativo.
Atenuações
Recomendamos atualizar para uma versão mais recente do SQL Server ou usar um nível de compatibilidade mais alto, para que o SQL gerado seja suportado. Dito isto, se você não puder fazer isso, por favor, comente sobre o problema de rastreamento com detalhes. Podemos rever esta decisão com base no feedback.
As informações/metadados da extensão foram removidos de IDbContextOptionsExtension
Issue de Acompanhamento #16119
Comportamento antigo
IDbContextOptionsExtension
continha métodos para fornecer metadados sobre a extensão.
Novo comportamento
Esses métodos foram movidos para uma nova classe base abstrata DbContextOptionsExtensionInfo
, que é retornada de uma nova propriedade IDbContextOptionsExtension.Info
.
Porquê
Ao longo das versões de 2.0 a 3.0, precisávamos adicionar ou alterar esses métodos várias vezes. Dividi-los em uma nova classe base abstrata tornará mais fácil fazer esse tipo de alterações sem quebrar as extensões existentes.
Atenuações
Atualize as extensões para seguir o novo padrão.
Exemplos são encontrados nas muitas implementações de IDbContextOptionsExtension
para diferentes tipos de extensões no código-fonte do EF Core.
O LogQueryPossibleExceptionWithAggregateOperator foi renomeado
Alteração
RelationalEventId.LogQueryPossibleExceptionWithAggregateOperator
foi renomeado para RelationalEventId.LogQueryPossibleExceptionWithAggregateOperatorWarning
.
Porquê
Alinha a nomenclatura desse evento de aviso com todos os outros eventos de aviso.
Atenuações
Use o novo nome. (Observe que o número de ID do evento não foi alterado.)
Esclarecer a API relativamente aos nomes de restrição de chave estrangeira
Problema de rastreamento #10730
Comportamento antigo
Antes do EF Core 3.0, os nomes de restrição de chave estrangeira eram chamados simplesmente de "nome". Por exemplo:
var constraintName = myForeignKey.Name;
Novo comportamento
A partir do EF Core 3.0, os nomes de restrição de chave estrangeira agora são chamados de "nome da restrição". Por exemplo:
var constraintName = myForeignKey.ConstraintName;
Porquê
Essa alteração traz consistência à nomenclatura nessa área e também esclarece que esse é o nome da restrição de chave estrangeira e não o nome da coluna ou propriedade na qual a chave estrangeira está definida.
Atenuações
Use o novo nome.
IRelationalDatabaseCreator.HasTables/HasTablesAsync foram tornados públicos
Questão de Acompanhamento #15997
Comportamento antigo
Antes do EF Core 3.0, esses métodos eram protegidos.
Novo comportamento
A partir do EF Core 3.0, esses métodos são públicos.
Porquê
Esses métodos são usados pelo EF para determinar se um banco de dados é criado, mas vazio. Isso também pode ser útil de fora do EF ao determinar se as migrações devem ou não ser aplicadas.
Atenuações
Altere a acessibilidade de quaisquer substituições.
Microsoft.EntityFrameworkCore.Design agora é um pacote DevelopmentDependency
Comportamento antigo
Antes do EF Core 3.0, Microsoft.EntityFrameworkCore.Design era um pacote NuGet regular cujo assembly podia ser referenciado por projetos que dependiam dele.
Novo comportamento
Começando com o EF Core 3.0, é um pacote DevelopmentDependency. Isso significa que a dependência não será propagada para outros projetos de forma transitiva e que você, por padrão, já não poderá referenciar diretamente a sua assembly.
Porquê
Este pacote destina-se apenas a ser usado na fase de design. Os aplicativos implantados não devem fazer referência a ele. Tornar o pacote uma dependência de desenvolvimento reforça esta recomendação.
Atenuações
Se precisar referenciar este pacote para substituir o comportamento na fase de design do EF Core, pode atualizar os metadados do item PackageReference no seu projeto.
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<!-- Remove IncludeAssets to allow compiling against the assembly -->
<!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>
Se o pacote estiver sendo referenciado transitivamente via Microsoft.EntityFrameworkCore.Tools, você precisará adicionar um PackageReference explícito ao pacote para alterar seus metadados. Essa referência explícita deve ser adicionada a qualquer projeto em que os tipos do pacote sejam necessários.
SQLitePCL.raw atualizado para a versão 2.0.0
Comportamento antigo
Microsoft.EntityFrameworkCore.Sqlite dependia anteriormente da versão 1.1.12 do SQLitePCL.raw.
Novo comportamento
Atualizámos o nosso pacote para depender da versão 2.0.0.
Porquê
A versão 2.0.0 do SQLitePCL.raw destina-se ao .NET Standard 2.0. Anteriormente, ele tinha como alvo o .NET Standard 1.1, que exigia um grande fechamento de pacotes transitivos para funcionar.
Atenuações
SQLitePCL.raw versão 2.0.0 inclui algumas alterações significativas. Consulte as notas de versão para obter detalhes.
NetTopologySuite atualizado para a versão 2.0.0
Comportamento antigo
Os pacotes espaciais dependiam anteriormente da versão 1.15.1 do NetTopologySuite.
Novo comportamento
Atualizamos nosso pacote para depender da versão 2.0.0.
Porquê
A versão 2.0.0 do NetTopologySuite visa resolver vários problemas de usabilidade encontrados pelos usuários do EF Core.
Atenuações
NetTopologySuite versão 2.0.0 inclui algumas alterações de quebra. Consulte as notas de versão para obter detalhes.
Microsoft.Data.SqlClient é usado em vez de System.Data.SqlClient
Comportamento antigo
Microsoft.EntityFrameworkCore.SqlServer dependia anteriormente de System.Data.SqlClient.
Novo comportamento
Atualizamos nosso pacote para depender de Microsoft.Data.SqlClient.
Porquê
Microsoft.Data.SqlClient é o principal driver de acesso a dados para o SQL Server no futuro, e System.Data.SqlClient não será mais o foco do desenvolvimento. Alguns recursos importantes, como Always Encrypted, só estão disponíveis em Microsoft.Data.SqlClient.
Atenuações
Se seu código tem uma dependência direta de System.Data.SqlClient, você deve alterá-lo para fazer referência a Microsoft.Data.SqlClient em vez disso; como os dois pacotes mantêm um grau muito alto de compatibilidade de API, isso deve ser apenas uma simples alteração de pacote e namespace.
Várias relações ambíguas de autorreferência devem ser configuradas
Comportamento antigo
Um tipo de entidade com várias propriedades de navegação unidirecional de autorreferência e FKs correspondentes foi configurado incorretamente como uma única relação. Por exemplo:
public class User
{
public Guid Id { get; set; }
public User CreatedBy { get; set; }
public User UpdatedBy { get; set; }
public Guid CreatedById { get; set; }
public Guid? UpdatedById { get; set; }
}
Novo comportamento
Esse cenário agora é detetado na construção do modelo e uma exceção é lançada, indicando que o modelo é ambíguo.
Porquê
O modelo resultante era ambíguo e provavelmente estará errado para este caso.
Atenuações
Utilize a configuração completa da relação. Por exemplo:
modelBuilder
.Entity<User>()
.HasOne(e => e.CreatedBy)
.WithMany();
modelBuilder
.Entity<User>()
.HasOne(e => e.UpdatedBy)
.WithMany();
DbFunction.Schema sendo nulo ou string vazia configura-o para que esteja no esquema padrão do modelo
Comportamento antigo
Um DbFunction configurado com esquema como uma cadeia de caracteres vazia foi tratado como função interna sem um esquema. Por exemplo, o código a seguir mapeará a função CLR DatePart
para a função interna DATEPART
no SQL Server.
[DbFunction("DATEPART", Schema = "")]
public static int? DatePart(string datePartArg, DateTime? date) => throw new Exception();
Novo comportamento
Todos os mapeamentos DbFunction são considerados mapeados para funções definidas pelo usuário. Portanto, o valor da cadeia de caracteres vazia colocaria a função dentro do esquema padrão para o modelo. O esquema pode ser configurado explicitamente via a API fluente modelBuilder.HasDefaultSchema()
ou, de outra forma, dbo
.
Porquê
Anteriormente, o facto de o esquema estar vazio era uma maneira de indicar que a função em questão era incorporada, mas essa lógica só é aplicável ao SqlServer, onde as funções incorporadas não pertencem a nenhum esquema.
Atenuações
Configure manualmente a tradução de DbFunction para mapeá-la para uma função interna.
modelBuilder
.HasDbFunction(typeof(MyContext).GetMethod(nameof(MyContext.DatePart)))
.HasTranslation(args => SqlFunctionExpression.Create("DatePart", args, typeof(int?), null));
EF Core 3.0 tem como alvo o .NET Standard 2.1 em vez de .NET Standard 2.0 Reverted
O EF Core 3.0 tem como alvo o .NET Standard 2.1, que é uma alteração significativa que exclui aplicativos do .NET Framework. O EF Core 3.1 reverteu isso e passou a direcionar .NET Standard 2.0 novamente.
A execução da consulta está a ser registada no nível de depuração revertida
Revertemos essa alteração porque a nova configuração no EF Core 3.0 permite que o nível de log para qualquer evento seja especificado pelo aplicativo. Por exemplo, para mudar o registo de SQL para Debug
, configure explicitamente o nível em OnConfiguring
ou AddDbContext
.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(connectionString)
.ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuting, LogLevel.Debug)));