82 votes

Linq - Confusion SelectMany

D'après ce que je comprends de la documentation de SelectMany, on pourrait l'utiliser pour produire une séquence (aplatie) d'une relation 1-many.

J'ai les classes suivantes

  public class Customer
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }

  class Order
  {
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public string Description { get; set; }
  }

J'essaie ensuite de les utiliser en utilisant la syntaxe des expressions de requête comme suit

  var customers = new Customer[]
  {
    new Customer() { Id=1, Name ="A"},
    new Customer() { Id=2, Name ="B"},
    new Customer() { Id=3, Name ="C"}
  };

  var orders = new Order[]
  {
    new Order { Id=1, CustomerId=1, Description="Order 1"},
    new Order { Id=2, CustomerId=1, Description="Order 2"},
    new Order { Id=3, CustomerId=1, Description="Order 3"},
    new Order { Id=4, CustomerId=1, Description="Order 4"},
    new Order { Id=5, CustomerId=2, Description="Order 5"},
    new Order { Id=6, CustomerId=2, Description="Order 6"},
    new Order { Id=7, CustomerId=3, Description="Order 7"},
    new Order { Id=8, CustomerId=3, Description="Order 8"},
    new Order { Id=9, CustomerId=3, Description="Order 9"}
  };

  var customerOrders = from c in customers
                       from o in orders
                       where o.CustomerId == c.Id
                       select new 
                              { 
                                 CustomerId = c.Id
                                 , OrderDescription = o.Description 
                              };

  foreach (var item in customerOrders)
    Console.WriteLine(item.CustomerId + ": " + item.OrderDescription);

Cela donne ce dont j'ai besoin.

1: Order 1
1: Order 2
1: Order 3
1: Order 4
2: Order 5
2: Order 6
3: Order 7
3: Order 8
3: Order 9

Je suppose que cela se traduit par l'utilisation de la méthode SelectMany lorsqu'on n'utilise pas la syntaxe d'expression de requête ?

Quoi qu'il en soit, j'essaie de me faire une idée de l'utilisation de SelectMany. Donc, même si ma requête ci-dessus ne se traduit pas en SelectMany, étant donné les deux classes et les données fictives, quelqu'un pourrait-il me fournir une requête linq qui utilise SelectMany ?

3 votes

Voir partie 41 de Jon Skeet Série Edulinq . Il explique le processus de traduction de l'expression de la requête.

2 votes

En y réfléchissant, voir aussi Partie 9 : SelectMany :)

4 votes

La série Edulinq de John Skeet est désormais disponible. ici .

101voto

Sapph Points 4040

Voici votre requête en utilisant SelectMany dont le modèle correspond exactement à votre exemple. Même résultat !

        var customerOrders2 = customers.SelectMany(
            c => orders.Where(o => o.CustomerId == c.Id),
            (c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });

Le premier argument associe chaque client à une collection de commandes (tout à fait analogue à la clause "where" que vous avez déjà).

Le deuxième argument transforme chaque paire appariée {(c1, o1), (c1, o2) .. (c3, o9)} en un nouveau type, que j'ai rendu identique à votre exemple.

Donc :

  • arg1 fait correspondre chaque élément de la collection de base à une autre collection.
  • arg2 (facultatif) transforme chaque paire en un nouveau type

La collection résultante est plate comme dans votre exemple original.

Si vous omettez le deuxième argument, vous obtiendrez une collection de toutes les commandes qui correspondent à un client. Ce serait juste ça, une collection plate de Order objets.

Il faut beaucoup de temps pour s'habituer à l'utiliser, j'ai encore du mal à m'y retrouver parfois. :(

2 votes

Merci pour votre réponse et votre explication. C'est exactement ce dont j'avais besoin. Je vous remercie également d'avoir fourni une réponse totalement adaptée au contexte de ma question, ce qui la rend d'autant plus facile à comprendre.

1 votes

Pour l'amour de Dieu, pourquoi mettre le .Where() à l'intérieur de le SelectMany() m'a échappé pendant si longtemps ?? Merci de me l'avoir signalé...

0 votes

Juste pour info, GroupBy pourrait être une meilleure option pour ce scénario particulier.

27voto

KeithS Points 36130

SelectMany() fonctionne comme Select, mais avec la possibilité supplémentaire d'aplatir une collection qui est sélectionnée. Elle doit être utilisée chaque fois que vous voulez une projection d'éléments de sous-collections, et que vous ne vous souciez pas de l'élément contenant la sous-collection.

Par exemple, disons que votre domaine ressemble à ceci :

public class Customer
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
  }

  class Order
  {
    public int Id { get; set; }
    public Customer Customer { get; set; }
    public string Description { get; set; }
  }

Pour obtenir la même liste que vous vouliez, votre Linq ressemblerait à quelque chose comme ceci :

var customerOrders = Customers
                        .SelectMany(c=>c.Orders)
                        .Select(o=> new { CustomerId = o.Customer.Id, 
                                           OrderDescription = o.Description });

... qui produira le même résultat sans avoir besoin de la collection plate des commandes. Le SelectMany prend la collection Orders de chaque client et l'examine de manière itérative afin de produire un résultat de type IEnumerable<Order> d'un IEnumerable<Customer> .

3 votes

"(...) et ne se soucient pas de l'élément contenant la sous-collection." Si vous voulez l'aplatissement, et que vous vous souciez de l'élément contenant, il existe une fonction surcharge de SelectMany pour cela :)

0 votes

@Keith merci pour votre réponse. Comment pourrais-je l'utiliser avec une collection plate de commandes ?

0 votes

Votre domaine semble un peu douteux. Une commande contient un client qui, à son tour, contient plusieurs commandes ?

5voto

George Points 95

Bien qu'il s'agisse d'une vieille question, j'ai pensé améliorer un peu les excellentes réponses :

SelectMany renvoie une liste (qui peut être vide) pour chaque élément de la liste de contrôle. Chaque élément de ces listes de résultats est énuméré dans la séquence de sortie des expressions et est donc concaténé dans le résultat. Par conséquent, a' list -> b' list[] -> concatenate -> b' list.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Diagnostics;
namespace Nop.Plugin.Misc.WebServices.Test
{
    [TestClass]
    public class TestBase
    {
        [TestMethod]
        public void TestMethod1()
        {  //See result in TestExplorer - test output 
            var a = new int[]{7,8};
            var b = new int[]
                    {12,23,343,6464,232,75676,213,1232,544,86,97867,43};
            Func<int, int, bool> numberHasDigit = 
                    (number
                     , digit) => 
                         ( number.ToString().Contains(digit.ToString()) );

            Debug.WriteLine("Unfiltered: All elements of 'b' for each element of 'a'");
            foreach(var l in a.SelectMany(aa => b))
                Debug.WriteLine(l);
            Debug.WriteLine(string.Empty);
            Debug.WriteLine("Filtered:" +  
            "All elements of 'b' for each element of 'a' filtered by the 'a' element");
            foreach(var l in a.SelectMany(aa => b.Where(bb => numberHasDigit(bb, aa))))
                Debug.WriteLine(l);
        }
    }
}

1voto

Voici une autre option utilisant SelectMany

var customerOrders = customers.SelectMany(
  c => orders.Where(o => o.CustomerId == c.Id)
    .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));

Si vous utilisez l'Entity Framework ou LINQ to Sql et que vous avez une association (relation) entre les entités, vous pouvez le faire :

var customerOrders = customers.SelectMany(
  c => c.orders
   .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X