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?

VansFannel
la source
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).

Ivan Stoev
la source
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

Rafael Merlin
la source
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

Dhunt
la source
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.

octavioccl
la source
5

J'ai trouvé la réponse:

codes = codesRepo.SearchFor(predicate)
      .ToList()
      .Select(c => Tuple.Create(c.Id, c.Flag))
      .ToList();
VansFannel
la source
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);
MohammadSoori
la source
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.

IbrarMumtaz
la source
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