D'après ce que j'ai compris de la documentation de SelectMany, on pourrait l'utiliser pour produire une séquence (aplatie) d'une relation à plusieurs.
J'ai cours suivants
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 d'expression de requête comme si
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 lorsque vous n'utilisez pas la syntaxe d'expression de requête?
Quoi qu'il en soit, j'essaie de comprendre ce que j'ai fait avec SelectMany. Donc, même si ma requête ci-dessus ne se traduit pas par SelectMany, étant donné les deux classes et les données fictives, quelqu'un pourrait-il me fournir une requête linq qui utilise SelectMany?
Voici votre requête utilisant SelectMany
, modélisé exactement d'après 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 établit une correspondance entre chaque client et une collection de commandes (tout à fait analogue à la clause "où" que vous avez déjà).
Le deuxième argument transforme chaque paire correspondante {(c1, o1), (c1, o2) .. (c3, o9)} en un nouveau type, que j'ai identique à votre exemple.
Alors:
La collection résultante est plate comme vous le souhaiteriez dans votre exemple d'origine.
Si vous omettiez le deuxième argument, vous obtiendriez une collection de toutes les commandes correspondant à un client. Ce serait juste cela, une collection plate d'objets Order
.
Il faut beaucoup de temps pour s’y habituer et j’ai toujours du mal à comprendre. :(
SelectMany () fonctionne comme Select, mais avec cette fonctionnalité supplémentaire d’aplatissement d’une collection sélectionnée. Il doit être utilisé chaque fois que vous souhaitez une projection d'éléments de sous-collections, sans vous soucier de l'élément contenant de la sous-collection.
Par exemple, supposons 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 à 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 de commandes à plat. SelectMany prend la collection de commandes de chaque client et effectue une itération pour produire un fichier IEnumerable<Order>
d'un IEnumerable<Customer>
.
Bien que ce soit une vieille question, je pensais 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, une liste 'liste -> b' [] -> concaténer -> b '.
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);
}
}
}
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 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}));