Criar uma tupla em um Linq Select

87

Estou trabalhando com C # e .NET Framework 4.5.1 recuperando dados de um banco de dados SQL Server com Entity Framework 6.1.3.

Eu tenho isto:

codes = codesRepo.SearchFor(predicate)
      .Select(c => new Tuple<string, byte>(c.Id, c.Flag))
      .ToList();

E quando eu o executo, recebo esta mensagem:

Apenas construtores e inicializadores sem parâmetros têm suporte no LINQ to Entities.

Não sei como devo criar a Tupla, porque todos os exemplos que encontrei são em sua maioria como este.

Eu tentei isso:

codes = codesRepo.SearchFor(predicate)
      .Select(c => Tuple.Create(c.Id, c.Flag))
      .ToList();

E obter este erro:

LINQ to Entities não reconhece o método 'System.Tuple`2 [System.String, System.Byte] Create [String, Byte] (System.String, Byte)' e este método não pode ser convertido em uma expressão de armazenamento.

Onde está o problema?

VansFannel
fonte
Parece que você precisará criar um objeto fortemente tipado. Mas sim, boa pergunta; votado.
francês

Respostas:

118

Embora a resposta por octavioccl funcione, é melhor primeiro projetar o resultado da consulta no tipo anônimo e, em seguida, mudar para enumerável e convertê-lo em tupla. Desta forma, a sua consulta recuperará da base de dados apenas os campos necessários.

codes = codesRepo.SearchFor(predicate)
    .Select(c => new { c.Id, c.Flag })
    .AsEnumerable()
    .Select(c => new Tuple<string, byte>(c.Id, c.Flag))
    .ToList();

Nota: A regra acima se aplica ao EF6. EF Core naturalmente suporta tuplas (em projeção ou como chaves de junção / grupo) via construtor de tupla, por exemplo, a consulta original simplesmente funciona

codes = codesRepo.SearchFor(predicate)
  .Select(c => new Tuple<string, byte>(c.Id, c.Flag))
  .ToList();

mas não o Tuple.Createmétodo (EF Core 2.x).

Ivan Stoev
fonte
Muito boa solução - Obrigado! ... E se eu tivesse que estender essa solução por outro valor anulável? .Select(c => new { c.Id, c.Flag, c.Foo?.Code })não funciona.
skyfrog de
2
@skyfrog O operador ?.não é compatível com árvores de expressão. Mas além disso, você pode estender o tipo anônimo com quantos valores quiser - apenas não se esqueça de nomeá-los quando necessário :) por exemploc => new { c.Id, c.Flag, Code = (int?)c.Foo.Code }
Ivan Stoev
1
Ótimo! Muito obrigado @Ivan por sua resposta. Eu estava tão perto! ... mas é sempre fácil dizer olhando para trás ;-)
skyfrog
Ótima resposta, pode ser usado com EF-Entities .. por exemplo, dbCtx.MyEntity.Where (). Select (.. to anon object ...). Etc ...
joedotnot
44

Apenas uma resposta atualizada para C # 7, agora você pode usar uma sintaxe mais simples para criar ValueTuples.

codes = codesRepo.SearchFor(predicate)
.Select(c => new { c.Id, c.Flag })
.AsEnumerable()
.Select(c => (c.Id, c.Flag))
.ToList();

Você pode até nomear as propriedades da tupla agora:

codes = codesRepo.SearchFor(predicate)
.Select(c => new { c.Id, c.Flag }) // anonymous type
.AsEnumerable()
.Select(c => (Id: c.Id, Flag: c.Flag)) // ValueTuple
.ToList();

Portanto, em vez de usá-lo como Item1 ou Item2, você pode acessá-lo como Id ou Flag.

Mais documentos sobre como escolher entre anônimo e tupla

Rafael Merlin
fonte
11

Experimente isto:

codes = codesRepo.SearchFor(predicate)
  .Select(c => Tuple.Create(c.Id, c.Flag))
  .ToList();

Foi informado que isso não está aceitando no LINQ para entidades.

Outra opção seria puxar o resultado para a memória antes de selecionar. Se você vai fazer isso, eu recomendaria fazer toda a filtragem antes de .AsEnumerable (), pois isso significa que você está apenas obtendo os resultados desejados, em vez de retirar toda a tabela e, em seguida, filtrar.

codes = codesRepo.SearchFor(predicate).AsEnumerable()
  .Select(c => Tuple.Create(c.Id, c.Flag))
  .ToList();

também Tuple.Create (c.Id, c.Flag) pode ser alterado para novo Tuple (c.Id, c.Flag) se você quiser tornar o código um pouco mais explícito nos tipos de tuplas

Dhunt
fonte
Desculpe, não funciona. Eu atualizei minha pergunta com mais detalhes.
VansFannel
11

Nas entidades linq to, você pode projetar em um tipo anônimo ou DTO. Para evitar esse problema, você pode usar o AsEnumerablemétodo de extensão:

codes = codesRepo.SearchFor(predicate).AsEnumerable().
      .Select(c => new Tuple<string, byte>(c.Id, c.Flag))
      .ToList();

Este método permite trabalhar com Linq to Object ao invés de Linq to Entities , então após chamá-lo, você pode projetar o resultado de sua consulta no que você precisar. A vantagem de usar em AsEnumerablevez disso ToListé que AsEnumerablenão executa a consulta, preserva a execução adiada. É uma boa ideia sempre filtrar seus dados antes de chamar um desses métodos.

octavioccl
fonte
5

Eu encontrei a resposta:

codes = codesRepo.SearchFor(predicate)
      .ToList()
      .Select(c => Tuple.Create(c.Id, c.Flag))
      .ToList();
VansFannel
fonte
Não, isso gerará SELECT *
Mihai Bratulescu
1

Use este método para fazer isso e use o assíncrono.

var codes = await codesRepo.SearchFor(predicate)
                    .Select(s => new
                    {
                        Id = s.Id,
                        Flag = s.Flag
                    }).FirstOrDefaultAsync();

                var return_Value = new Tuple<string, byte>(codes.Id, codes.Flag);
MohammadSoori
fonte
0

Apenas meus dois centavos: isso me surpreendeu algumas vezes com os nomes dos tipos:

Alguns exemplos complicados:

    private Tuple<string, byte> v1()
    {
        return new Tuple<string, byte>("", 1);
    }

    private (string, int) v2()
    {
        return ("", 1);
    }

    private (string Id, byte Flag) v3()
    {
        return ("", 1);
    }

Saudações.

IbrarMumtaz
fonte
A sintaxe que você postou não funciona. O que você provavelmente pretendia escrever é public (string Id, byte Flag) SearchFor(Expression predicate), mas isso não vem ao caso. Dois centavos não devem ser uma resposta, mas um comentário.
M.Stramm de
2
Atualizei minha resposta - deveria ter verificado antes de postar. Discordo; todas as informações são úteis para todos os visitantes que chegam a esta página, independentemente de como sejam postadas. Os comentários não transmitem a intenção, bem como respondem, graças às respostas.
IbrarMumtaz de
Eu concordo que o conteúdo adicionado é bom e os comentários não atendem bem aos exemplos de código. Obrigado pela edição, agora está claro que esta não é uma resposta à pergunta do OP (mas pode ajudar com problemas relacionados à tupla).
M.Stramm