Compartilhar via


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 da falha Impacto
O EF Core 5.0 não dá suporte ao .NET Framework Médio
IProperty.GetColumnName() agora está obsoleto Médio
Precisão e escala são necessárias para decimais Médio
A navegação necessária ou não anulável de entidade de segurança para dependente tem semântica diferente Médio
A consulta de definição é substituída por métodos específicos do provedor Médio
As navegações de referência não nulas não são substituídas por consultas Médio
ToView() é tratado de forma diferente por migrações Médio
ToTable(null) marca o tipo de entidade como não mapeado para uma tabela Médio
O método HasGeometricDimension foi removido da extensão NTS do SQLite Baixo
Azure Cosmos DB: a chave de partição agora é adicionada à chave primária Baixo
Azure Cosmos DB: propriedade id renomeada para __id Baixo
Azure Cosmos DB: byte[] agora é armazenado como uma cadeia de caracteres base64 em vez de uma matriz de números Baixo
Azure Cosmos DB: GetPropertyName e SetPropertyName foram renomeados Baixo
Os geradores de valor são chamados quando o estado da entidade é alterado de Desanexado para Inalterado, Atualizado ou Excluído Baixo
IMigrationsModelDiffer agora usa IRelationalModel Baixo
Os discriminadores são somente leitura Baixo
Métodos EF.Functions específicos do provedor lançados para o provedor InMemory Baixo
IndexBuilder.HasName agora está obsoleto Baixo
Um pluralizador agora está incluído para scaffolding de modelos de engenharia reversa Baixo
INavigationBase substitui INavigation em algumas APIs para dar suporte a navegações de salto (navegações skip) Baixo
Algumas consultas com coleção correlacionada que também usam Distinct ou GroupBy não têm mais suporte Baixo
Não há suporte para o uso de uma coleção de tipo passível de consulta na projeção Baixo

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étodo Select, 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 Distinctna 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())