Créer un tuple dans un Linq Select


87

Je travaille avec C # et .NET Framework 4.5.1 pour récupérer des données à partir d'une base de données SQL Server avec Entity Framework 6.1.3.

J'ai ceci:

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

Et quand je le lance, je reçois ce message:

Seuls les constructeurs et initialiseurs sans paramètre sont pris en charge dans LINQ to Entities.

Je ne sais pas comment je dois créer le Tuple car tous les exemples que j'ai trouvés sont pour la plupart comme celui-ci.

J'ai essayé ceci:

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

Et obtenez cette erreur:

LINQ to Entities ne reconnaît pas la méthode 'System.Tuple`2 [System.String, System.Byte] Create [String, Byte] (System.String, Byte)', et cette méthode ne peut pas être traduite en une expression de magasin.

Où est le problème?


Il semble que vous deviez créer un objet fortement typé. Mais oui, bonne question; voté.
frenchie

Réponses:


118

Bien que la réponse d' octavioccl fonctionne, il est préférable de projeter d'abord le résultat de la requête en type anonyme, puis de passer à énumérable et de le convertir en tuple. De cette façon, votre requête ne récupérera de la base de données que les champs nécessaires.

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

Remarque: la règle ci-dessus s'applique à EF6. EF Core prend naturellement en charge les tuples (en projection ou en tant que clés de jointure / groupe) via le constructeur de tuple, par exemple la requête d'origine fonctionne simplement

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

mais pas la Tuple.Createméthode (EF Core 2.x).


Très bonne solution - Merci! ... Et si j'avais dû étendre cette solution par une autre valeur nullable? .Select(c => new { c.Id, c.Flag, c.Foo?.Code })ne marche pas.
skyfrog

2
@skyfrog L'opérateur ?.n'est pas pris en charge dans les arborescences d'expression. Mais à part cela, vous pouvez étendre le type anonyme avec autant de valeurs que vous voulez - juste ne pas oublier de les nommer si nécessaire :) par exemplec => new { c.Id, c.Flag, Code = (int?)c.Foo.Code }
Ivan Stoev

1
Génial! Merci beaucoup @Ivan pour votre réponse. J'étais si près! ... mais c'est toujours facile à dire en regardant en arrière ;-)
skyfrog

Excellente réponse, peut être utilisé avec EF-Entities .. par exemple dbCtx.MyEntity.Where (). Select (.. to anon object ...). Etc ...
joedotnot

44

Juste une réponse mise à jour pour C # 7, vous pouvez maintenant utiliser une syntaxe plus simple pour créer des ValueTuples.

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

Vous pouvez même nommer les propriétés du tuple maintenant:

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

Ainsi, au lieu de l'utiliser comme Item1 ou Item2, vous pouvez y accéder comme Id ou Flag.

Plus de documentation sur le choix entre anonyme et tuple


11

Essaye ça:

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

Vous avez été informé que cela n'accepte pas dans LINQ les entités.

Une autre option serait de mettre le résultat en mémoire avant de le sélectionner. Si vous envisagez de faire cela, je vous recommanderais de faire tout le filtrage avant le .AsEnumerable () car cela signifie que vous ne récupérez que les résultats que vous voulez plutôt que de retirer toute la table, puis de filtrer.

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

ainsi Tuple.Create (c.Id, c.Flag) pourrait être changé en new Tuple (c.Id, c.Flag) si vous voulez rendre le code un peu plus explicite dans les types de tuples


Désolé, ça ne marche pas. J'ai mis à jour ma question avec plus de détails.
VansFannel

11

Dans linq aux entités, vous pouvez projeter sur un type anonyme ou sur un DTO.Pour éviter ce problème, vous pouvez utiliser la AsEnumerableméthode d'extension:

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

Cette méthode vous permet de travailler avec Linq to Object au lieu de Linq to Entities , donc après l'avoir appelée, vous pouvez projeter le résultat de votre requête dans tout ce dont vous avez besoin.L'avantage d'utiliser à la AsEnumerableplace ToListest que AsEnumerablecela n'exécute pas la requête, cela préserve l'exécution différée. C'est une bonne idée de toujours filtrer vos données avant d'appeler l'une de ces méthodes.


5

J'ai trouvé la réponse:

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

Non, cela générera SELECT *
Mihai Bratulescu le

1

Utilisez cette méthode pour ce faire et utilisez le fichier async.

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);

0

Juste mes deux cents: cela m'a surpris à quelques reprises avec les noms de type:

Quelques exemples noddy:

    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);
    }

Cordialement.


La syntaxe que vous avez publiée ne fonctionne pas. Ce que vous vouliez probablement écrire est public (string Id, byte Flag) SearchFor(Expression predicate), mais ce n'est pas la question. Deux cents ne devraient pas être une réponse, mais un commentaire.
M.Stramm

2
J'ai mis à jour ma réponse - j'aurais dû la vérifier avant de publier. Je ne suis pas d'accord; toutes les informations sont utiles à tous les visiteurs qui arrivent sur cette page, quelle que soit la façon dont elle est présentée. Les commentaires ne transmettent pas aussi bien l'intention que la réponse grâce aux réponses.
IbrarMumtaz

Je conviens que le contenu ajouté est bon et que les commentaires ne conviennent pas bien aux exemples de code. Merci pour l'édition, il est maintenant clair que ce n'est pas une réponse à la question du PO (mais peut aider avec les problèmes liés aux tuple).
M.Stramm
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.