Alterações interruptivas no EF Core 5.0
As seguintes alterações de API e comportamento têm o potencial de interromper aplicativos existentes ao atualizar para o EF Core 5.0.0.
Resumo
Alterações de impacto médio
O EF Core 5.0 não dá suporte ao .NET Framework
Problema de acompanhamento nº 15498
Comportamento antigo
O EF Core 3.1 visa ao .NET Standard 2.0, que é compatível com o .NET Framework.
Novo comportamento
O EF Core 5.0 visa ao .NET Standard 2.1, que não tem suporte no .NET Framework. Isso significa que o EF Core 5.0 não pode ser usado com aplicativos do .NET Framework.
Por que
Isso faz parte do movimento mais amplo entre as equipes do .NET destinadas à unificação para uma única estrutura de destino do .NET. Para obter mais informações, consulte o futuro do .NET Standard.
Atenuações
Os aplicativos do .NET Framework podem continuar a usar o EF Core 3.1, que é uma versão LTS (suporte de longo prazo). Como alternativa, os aplicativos podem ser atualizados para usar o .NET Core 3.1 ou para o .NET 5, ambos compatíveis com o .NET Standard 2.1.
IProperty.GetColumnName() agora está obsoleto
Problema de acompanhamento nº 2266
Comportamento antigo
GetColumnName()
retornava o nome da coluna para a qual uma propriedade é mapeada.
Novo comportamento
GetColumnName()
ainda retorna o nome de uma coluna para a qual uma propriedade é mapeada, mas esse comportamento agora é ambíguo, pois o EF Core 5 dá suporte à TPT e ao mapeamento simultâneo para uma exibição ou para uma função em que esses mapeamentos poderiam usar nomes de coluna diferentes para a mesma propriedade.
Por que
Marcamos esse método como obsoleto para orientar os usuários a uma sobrecarga mais precisa – GetColumnName(IProperty, StoreObjectIdentifier).
Atenuações
Se o tipo de entidade for mapeado apenas para uma única tabela e nunca para exibições, funções ou várias tabelas, o GetColumnBaseName(IReadOnlyProperty)poderá ser usado no EF Core 5.0 e 6.0 para obter o nome da tabela. Por exemplo:
var columnName = property.GetColumnBaseName();
No EF Core 7.0, isso pode ser substituído novamente pelo novo GetColumnName
, que se comporta como o original fez para mapeamentos simples de tabela única.
Se o tipo de entidade puder ser mapeado para exibições, funções ou várias tabelas, será necessário obter uma StoreObjectIdentifier para identificar a tabela, exibição ou função. Em seguida, isso pode ser usado para obter o nome da coluna desse objeto de repositório. Por exemplo:
var columnName = property.GetColumnName(StoreObjectIdentifier.Table("Users", null)));
Precisão e escala são necessárias para decimais
Problema de acompanhamento nº 19293
Comportamento antigo
O EF Core normalmente não definia a precisão e a escala em objetos SqlParameter. Isso significa que a precisão e a escala completas eram enviadas para o SQL Server, momento em que o SQL Server arredondaria com base na precisão e escala da coluna de banco de dados.
Novo comportamento
O EF Core agora define a precisão e a escala em parâmetros usando os valores configurados para propriedades no modelo do EF Core. Isso significa que o arredondamento agora acontece no SqlClient. Consequentemente, se a precisão e a escala configuradas não corresponderem à precisão e escala do banco de dados, o arredondamento visto poderá mudar.
Por que
Os recursos mais recentes do SQL Server, incluindo o Always Encrypted, exigem que as facetas de parâmetro sejam totalmente especificadas. Além disso, o SqlClient fez uma alteração para arredondar em vez de truncar valores decimais, correspondendo assim ao comportamento do SQL Server. Isso possibilitou que o EF Core definisse essas facetas sem alterar o comportamento de decimais configuradas corretamente.
Atenuações
Mapeie suas propriedades decimais usando um nome de tipo que inclua precisão e escala. Por exemplo:
public class Blog
{
public int Id { get; set; }
[Column(TypeName = "decimal(16, 5)")]
public decimal Score { get; set; }
}
Ou use HasPrecision
nas APIs de criação de modelo. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(e => e.Score).HasPrecision(16, 5);
}
A navegação necessária ou não anulável de entidade de segurança para dependente tem semântica diferente
Problema de acompanhamento nº 17286
Comportamento antigo
Somente as navegações para a entidade de segurança podem ser configuradas conforme necessário. Portanto, usar RequiredAttribute
na navegação para o dependente (a entidade que contém a chave estrangeira) ou marcá-la como não anulável, em vez disso, criaria a chave estrangeira no tipo de entidade definidor.
Novo comportamento
Com o suporte adicionado para dependentes necessários, agora é possível marcar qualquer navegação de referência como necessária, o que significa que, no caso mostrado acima, a chave estrangeira será definida do outro lado da relação e as propriedades não serão marcadas como necessárias.
Chamar IsRequired
antes de especificar o fim dependente ficou agora ambíguo:
modelBuilder.Entity<Blog>()
.HasOne(b => b.BlogImage)
.WithOne(i => i.Blog)
.IsRequired()
.HasForeignKey<BlogImage>(b => b.BlogForeignKey);
Por que
O novo comportamento é necessário para habilitar o suporte para dependentes necessários (consulte nº 12100).
Atenuações
Remova RequiredAttribute
da navegação para o dependente e coloque-o na navegação para a entidade de segurança ou configure a relação em OnModelCreating
:
modelBuilder.Entity<Blog>()
.HasOne(b => b.BlogImage)
.WithOne(i => i.Blog)
.HasForeignKey<BlogImage>(b => b.BlogForeignKey)
.IsRequired();
A consulta definidora é substituída por métodos específicos do provedor
Problema de acompanhamento nº 18903
Comportamento antigo
Os tipos de entidade foram mapeados para consultas definidoras no nível do Core. Sempre que o tipo de entidade era usado na raiz da consulta do tipo de entidade era substituído pela consulta de definição para qualquer provedor.
Novo comportamento
AS APIs para consultas de definição foram preteridas. Novas APIs específicas do provedor foram introduzidas.
Por que
Embora a definição de consultas tenha sido implementada como consulta de substituição sempre que a raiz da consulta é usada na consulta, houve alguns problemas:
- Se a definição de consulta estiver projetando o tipo de entidade usando
new { ... }
no métodoSelect
, identificar isso como uma entidade exigiu trabalho adicional e o tornou inconsistente com a forma como o EF Core trata tipos nominais na consulta. - Para provedores relacionais,
FromSql
ainda é necessário para passar a cadeia de caracteres SQL no formulário de expressão LINQ.
Inicialmente, as consultas de definição foram introduzidas como exibições do lado do cliente a serem usadas com o provedor In-Memory para entidades sem chave (semelhante a exibições de banco de dados em bancos de dados relacionais). Tal definição facilita o teste do aplicativo no banco de dados na memória. Posteriormente, essas consultas tornaram-se amplamente aplicáveis, o que foi útil, mas resultou em um comportamento inconsistente e difícil de entender. Então decidimos simplificar o conceito. Tornamos as consultas de definição baseadas em LINQ exclusivas para o provedor In-Memory e as tratamos de forma diferente. Para obter mais informações, confira esta edição.
Atenuações
Para provedores relacionais, use o método ToSqlQuery
em OnModelCreating
e passe uma cadeia de caracteres SQL a ser usada para o tipo de entidade.
Para o provedor In-Memory, use o método ToInMemoryQuery
em OnModelCreating
e passe uma consulta LINQ a ser usada para o tipo de entidade.
As navegações de referência não nulas não são substituídas por consultas
Problema de acompanhamento nº 2693
Comportamento antigo
No EF Core 3.1, as navegações de referência inicializadas ansiosamente para valores não nulos às vezes seriam substituídas por instâncias de entidade do banco de dados, independentemente de os valores-chave corresponderem ou não. No entanto, em outros casos, o EF Core 3.1 faria o oposto e deixaria o valor não nulo existente.
Novo comportamento
A partir do EF Core 5.0, as navegações de referência não nulas nunca são substituídas por instâncias retornadas de uma consulta.
Observe que ainda há suporte para a inicialização ansiosa de uma navegação de coleção para uma coleção vazia.
Por que
A inicialização de uma propriedade de navegação de referência para uma instância de entidade "vazia" resulta em um estado ambíguo. Por exemplo:
public class Blog
{
public int Id { get; set; }
public Author Author { get; set; ) = new Author();
}
Normalmente, uma consulta para Blogs e Autores criará primeiramente instâncias Blog
e definirá as instâncias Author
apropriadas com base nos dados retornados do banco de dados. No entanto, nesse caso, cada propriedade Blog.Author
já está inicializada em um Author
vazio. Exceto que o EF Core não tem como saber que essa instância está "vazia". Portanto, substituir essa instância poderia potencialmente silenciosamente jogar fora um Author
válido. Portanto, o EF Core 5.0 agora não substitui consistentemente uma navegação que já está inicializada.
Esse novo comportamento também se alinha ao comportamento do EF6 na maioria dos casos, embora após a investigação também encontramos alguns casos de inconsistência no EF6.
Atenuações
Se essa interrupção for encontrada, a correção será parar de inicializar ansiosamente as propriedades de navegação de referência.
ToView() é tratado de forma diferente por migrações
Problema de acompanhamento nº 2725
Comportamento antigo
A chamada ToView(string)
fez com que as migrações ignorassem o tipo de entidade, além de mapeá-lo para uma exibição.
Novo comportamento
Agora ToView(string)
marca o tipo de entidade como não mapeado para uma tabela, além de mapeá-lo para uma exibição. O resultado é que a primeira migração após a atualização para o EF Core 5 vai tentar remover a tabela padrão para esse tipo de entidade, pois ela não é mais ignorada.
Por que
O EF Core agora permite que um tipo de entidade seja mapeado para uma tabela e uma exibição simultaneamente, portanto ToView
não é mais um indicador válido de que ele deve ser ignorado pelas migrações.
Atenuações
Use o seguinte código para marcar a tabela mapeada como excluída das migrações:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}
ToTable(null) marca o tipo de entidade como não mapeado para uma tabela
Problema de acompanhamento nº 21172
Comportamento antigo
ToTable(null)
redefinia o nome da tabela para o padrão.
Novo comportamento
ToTable(null)
agora marca o tipo de entidade como não mapeado para uma tabela.
Por que
O EF Core agora permite que um tipo de entidade seja mapeado para uma tabela e uma exibição simultaneamente, portanto ToTable(null)
é usado para indicar que ele não está mapeado para nenhuma tabela.
Atenuações
Use o código a seguir para redefinir o nome da tabela para o padrão se ele não for mapeado para uma exibição ou para uma DbFunction:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Metadata.RemoveAnnotation(RelationalAnnotationNames.TableName);
}
Alterações de baixo impacto
Método HasGeometricDimension removido da extensão NTS do SQLite
Problema de acompanhamento nº 14257
Comportamento antigo
HasGeometricDimension foi usado para habilitar dimensões adicionais (Z e M) em colunas de geometria. No entanto, isso só afetou a criação do banco de dados. Não era necessário especificá-la para consultar valores com dimensões adicionais. Esse método também não funcionava corretamente ao inserir ou atualizar valores com dimensões adicionais (consulte nº 14257).
Novo comportamento
Para habilitar a inserção e atualização de valores de geometria com dimensões adicionais (Z e M), a dimensão precisa ser especificada como parte do nome do tipo de coluna. Essa API corresponde mais de perto ao comportamento subjacente da função AddGeometryColumn do SpatiaLite.
Por que
Usar HasGeometricDimension depois de especificar a dimensão no tipo de coluna é desnecessário e redundante, portanto, removemos HasGeometricDimension inteiramente.
Atenuações
Use HasColumnType
para especificar a dimensão:
modelBuilder.Entity<GeoEntity>(
x =>
{
// Allow any GEOMETRY value with optional Z and M values
x.Property(e => e.Geometry).HasColumnType("GEOMETRYZM");
// Allow only POINT values with an optional Z value
x.Property(e => e.Point).HasColumnType("POINTZ");
});
Azure Cosmos DB: a chave de partição agora é adicionada à chave primária
Problema de acompanhamento nº 15289
Comportamento antigo
A propriedade de chave de partição só foi adicionada à chave alternativa que inclui id
.
Novo comportamento
A propriedade de chave de partição agora também é adicionada à chave primária por convenção.
Por que
Essa alteração torna o modelo mais alinhado com a semântica do Azure Cosmos DB e melhora o desempenho de Find
e de algumas consultas.
Atenuações
Para impedir que a propriedade de chave de partição seja adicionada à chave primária, configure-a em OnModelCreating
.
modelBuilder.Entity<Blog>()
.HasKey(b => b.Id);
Azure Cosmos DB: propriedade id
renomeada para __id
Problema de acompanhamento nº 17751
Comportamento antigo
A propriedade de sombra mapeada para a propriedade JSON id
também era chamada de id
.
Novo comportamento
A propriedade de sombra criada por convenção agora é chamada de __id
.
Por que
Essa alteração torna menos provável que a propriedade id
entre em conflito com uma propriedade existente no tipo de entidade.
Atenuações
Para voltar ao comportamento 3.x, configure a propriedade id
em OnModelCreating
.
modelBuilder.Entity<Blog>()
.Property<string>("id")
.ToJsonProperty("id");
Azure Cosmos DB: byte[] agora é armazenado como uma cadeia de caracteres base64 em vez de uma matriz de números
Problema de acompanhamento nº 17306
Comportamento antigo
As propriedades do tipo byte[] eram armazenadas como uma matriz de números.
Novo comportamento
As propriedades do tipo byte[] agora são armazenadas como uma cadeia de caracteres base64.
Por que
Essa representação de bytes[] alinha-se melhor às expectativas e é o comportamento padrão das principais bibliotecas de serialização JSON.
Atenuações
Os dados existentes armazenados como matrizes de números ainda serão consultados corretamente, mas atualmente não há uma maneira compatível de alterar o comportamento de inserção. Se essa limitação estiver bloqueando seu cenário, comente sobre esse problema
Azure Cosmos DB: GetPropertyName e SetPropertyName foram renomeados
Problema de acompanhamento nº 17874
Comportamento antigo
Anteriormente, os métodos de extensão eram chamados GetPropertyName
e SetPropertyName
Novo comportamento
A API antiga foi removida e novos métodos foram adicionados: GetJsonPropertyName
, SetJsonPropertyName
Por que
Essa alteração remove a ambiguidade em torno do que esses métodos estão configurando.
Atenuações
Use a nova API.
Os geradores de valor são chamados quando o estado da entidade é alterado de Desanexado para Inalterado, Atualizado ou Excluído
Problema de acompanhamento nº 15289
Comportamento antigo
Os geradores de valor só eram chamados quando o estado da entidade era alterado para Adicionado.
Novo comportamento
Os geradores de valor agora são chamados quando o estado da entidade é alterado de Desanexado para Inalterado, Atualizado ou Excluído e a propriedade contém os valores padrão.
Por que
Essa alteração foi necessária para melhorar a experiência com propriedades que não persistem no armazenamento de dados e têm seu valor gerado sempre no cliente.
Atenuações
Para impedir que o gerador de valor seja chamado, atribua um valor não padrão à propriedade antes que o estado seja alterado.
IMigrationsModelDiffer agora usa IRelationalModel
Problema de acompanhamento nº 20305
Comportamento antigo
A API IMigrationsModelDiffer
era definida usando IModel
.
Novo comportamento
A API IMigrationsModelDiffer
agora usa IRelationalModel
. No entanto, o instantâneo do modelo ainda contém apenas IModel
porque esse código faz parte do aplicativo e o Entity Framework não pode alterá-lo sem fazer uma alteração significativa maior.
Por que
IRelationalModel
é uma representação recém-adicionada do esquema de banco de dados. Usá-lo para encontrar diferenças é mais rápido e preciso.
Atenuações
Use o código a seguir para comparar o modelo de snapshot
com o modelo de context
:
var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();
var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);
var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);
var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var hasDifferences = modelDiffer.HasDifferences(
((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
context.Model.GetRelationalModel());
Estamos planejando melhorar essa experiência no 6.0 (consulte o nº 22031)
Os discriminadores são somente leitura
Problema de acompanhamento nº 21154
Comportamento antigo
Era possível alterar o valor discriminatório antes de chamar SaveChanges
Novo comportamento
Uma exceção será lançada no caso acima.
Por que
O EF não espera que o tipo de entidade mude enquanto ele ainda está sendo rastreado, portanto, alterar o valor discriminatório deixa o contexto em um estado inconsistente, o que pode resultar em um comportamento inesperado.
Atenuações
Se a alteração do valor discriminatório for necessária e o contexto for descartado imediatamente após chamar SaveChanges
, o discriminatório poderá se tornar mutável:
modelBuilder.Entity<BaseEntity>()
.Property<string>("Discriminator")
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
Métodos EF.Functions específicos do provedor lançados para o provedor InMemory
Problema de acompanhamento nº 20294
Comportamento antigo
Os métodos EF.Functions específicos do provedor continham implementação para execução do cliente, o que permitiu que eles fossem executados no provedor InMemory. Por exemplo, EF.Functions.DateDiffDay
é um método específico do Sql Server, que funcionava no provedor InMemory.
Novo comportamento
Métodos específicos do provedor foram atualizados para gerar uma exceção no corpo do método para bloquear a avaliação no lado do cliente.
Por que
Os métodos específicos do provedor são mapeados para uma função de banco de dados. A computação feita pela função de banco de dados mapeada nem sempre pode ser replicada no lado do cliente no LINQ. Isso pode fazer com que o resultado do servidor seja diferente ao executar o mesmo método no cliente. Como esses métodos são usados no LINQ para traduzir para funções de banco de dados específicas, eles não precisam ser avaliados no lado do cliente. Como o provedor InMemory é um banco de dados diferente, esses métodos não estão disponíveis para esse provedor. Tentar executá-los para o provedor InMemory ou qualquer outro provedor que não traduz esses métodos lança uma exceção.
Atenuações
Como não há como imitar o comportamento das funções de banco de dados com precisão, você deve testar as consultas que as contêm no mesmo tipo de banco de dados que em produção.
IndexBuilder.HasName agora está obsoleto
Problema de acompanhamento nº 21089
Comportamento antigo
Anteriormente, apenas um índice poderia ser definido em um determinado conjunto de propriedades. O nome do banco de dados de um índice era configurado usando IndexBuilder.HasName.
Novo comportamento
Vários índices agora são permitidos no mesmo conjunto de propriedades. Esses índices agora são diferenciados por um nome no modelo. Por convenção, o nome do modelo é usado como o nome do banco de dados; no entanto, ele também pode ser configurado de forma independente usando HasDatabaseName.
Por que
No futuro, gostaríamos de habilitar índices crescentes e decrescentes ou índices com ordenações diferentes no mesmo conjunto de propriedades. Essa alteração nos move mais uma etapa nessa direção.
Atenuações
Qualquer código que estava chamando anteriormente IndexBuilder.HasName deve ser atualizado para chamar HasDatabaseName.
Se o projeto incluir migrações geradas antes do EF Core versão 2.0.0, você poderá ignorar com segurança o aviso nesses arquivos e suprimi-lo adicionando #pragma warning disable 612, 618
.
Um pluralizador agora está incluído para o scaffolding de modelos de engenharia reversa
Problema de acompanhamento nº 11160
Comportamento antigo
Anteriormente, você precisava instalar um pacote de pluralizador separado para pluralizar nomes de navegação de coleção e DbSet e singularizar nomes de tabela ao fazer scaffolding de um DbContext e tipos de entidade por meio da engenharia reversa de um esquema de banco de dados.
Novo comportamento
O EF Core agora inclui um pluralizador que usa a biblioteca Humanizer. Essa é a mesma biblioteca que o Visual Studio usa para recomendar nomes de variáveis.
Por que
Usar formas plurais de palavras para propriedades de coleção e formulários singulares para tipos e propriedades de referência é idiomático no .NET.
Atenuações
Para desabilitar o pluralizador, use a opção --no-pluralize
em dotnet ef dbcontext scaffold
ou a opção -NoPluralize
em Scaffold-DbContext
.
INavigationBase substitui INavigation em algumas APIs para dar suporte a navegações de salto
Problema de acompanhamento nº 2568
Comportamento antigo
O EF Core anterior à 5.0 dava suporte apenas a uma forma de propriedade de navegação, representada pela interface INavigation
.
Novo comportamento
O EF Core 5.0 introduz relações muitos para muitos que usam "navegações skip". Elas são representadas pela interface ISkipNavigation
e a maior parte da funcionalidade de INavigation
foi movida para uma interface base comum: INavigationBase
.
Por que
A maioria das funcionalidades entre navegações normais e navegações skip é a mesma. No entanto, as navegações skip tem uma relação diferente com as chaves estrangeiras do que as navegações normais, uma vez que os FKs envolvidos não estão diretamente em nenhuma das extremidades da relação, mas sim na entidade de junção.
Atenuações
Em muitos casos, os aplicativos podem mudar para o uso da nova interface base sem outras alterações. No entanto, nos casos em que a navegação é usada para acessar propriedades de chave estrangeira, o código do aplicativo deve ser restrito apenas a navegações normais ou atualizado para fazer a coisa apropriada para navegações normal e skip.
Algumas consultas com coleção correlacionada que também usam Distinct
ou GroupBy
não têm mais suporte
Problema de acompanhamento nº 15873
Comportamento antigo
Anteriormente, as consultas envolvendo coleções correlacionadas eram seguidas por GroupBy
, bem como algumas consultas usando Distinct
que tínhamos a permissão de executar.
Exemplo de GroupBy:
context.Parents
.Select(p => p.Children
.GroupBy(c => c.School)
.Select(g => g.Key))
Distinct
exemplo – especificamente consultas Distinct
em que a projeção da coleção interna não contém a chave primária:
context.Parents
.Select(p => p.Children
.Select(c => c.School)
.Distinct())
Essas consultas poderiam retornar resultados incorretos se a coleção interna contivesse duplicatas, mas funcionavam corretamente se todos os elementos da coleção interna fossem exclusivos.
Comportamento novo
Essas consultas não têm mais suporte. A exceção é lançada indicando que não temos informações suficientes para compilar corretamente os resultados.
Por que
Para cenários de coleção correlacionados, precisamos conhecer a chave primária da entidade para atribuir entidades de coleção ao pai correto. Quando a coleção interna não usa GroupBy
ou Distinct
, a chave primária ausente pode simplesmente ser adicionada à projeção. No entanto, no caso de GroupBy
e Distinct
, isso não pode ser feito porque alteraria o resultado de GroupBy
ou a da operação Distinct
.
Mitigações
Reescreva a consulta para não usar a operação GroupBy
nem a Distinct
na coleção interna e execute essas operações no cliente.
context.Parents
.Select(p => p.Children.Select(c => c.School))
.ToList()
.Select(x => x.GroupBy(c => c).Select(g => g.Key))
context.Parents
.Select(p => p.Children.Select(c => c.School))
.ToList()
.Select(x => x.Distinct())
Não há suporte para o uso de uma coleção de tipo Queryable na projeção
Problema de acompanhamento nº 16314
Comportamento antigo
Anteriormente, era possível usar a coleção de um tipo Queryable dentro da projeção em alguns casos, por exemplo, como um argumento para um construtor List<T>
:
context.Blogs
.Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))
Comportamento novo
Essas consultas não têm mais suporte. A exceção é lançada indicando que não é possível criar um objeto do tipo Queryable e sugerindo como isso pode ser corrigido.
Por que
Não é possível materializar um objeto de um tipo Queryable, portanto, eles seriam criados automaticamente usando, em vez disso, o tipo List<T>
. Isso geralmente causaria uma exceção devido à incompatibilidade de tipos que não era muito clara e poderia ser surpreendente para alguns usuários. Decidimos reconhecer o padrão e lançar uma exceção mais significativa.
Mitigações
Adicione uma chamada ToList()
após o objeto Queryable na projeção:
context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())