Z tego, co rozumiem z dokumentacji SelectMany, można go użyć do stworzenia (spłaszczonej) sekwencji relacji 1-wiele.
Mam następujące zajęcia
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; }
}
Następnie próbuję ich użyć, używając składni wyrażenia zapytania w ten sposób
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);
To daje mi to, czego potrzebuję.
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
Zakładam, że przekłada się to na użycie metody SelectMany, gdy nie używa się składni wyrażenia zapytania?
Tak czy inaczej, próbuję obejść się za pomocą SelectMany. Więc nawet jeśli moje powyższe zapytanie nie jest tłumaczone na SelectMany, biorąc pod uwagę dwie klasy i pozorowane dane, czy ktoś mógłby mi podać zapytanie linq, które używa SelectMany?
c#
linq
data-structures
linq-to-objects
Jackie Kirby
źródło
źródło
Odpowiedzi:
Oto twoje zapytanie
SelectMany
, wzorowane dokładnie na twoim przykładzie. To samo wyjście!var customerOrders2 = customers.SelectMany( c => orders.Where(o => o.CustomerId == c.Id), (c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });
Pierwszy argument odwzorowuje każdego klienta na zbiór zamówień (całkowicie analogiczny do już posiadanej klauzuli „gdzie”).
Drugi argument przekształca każdą dopasowaną parę {(c1, o1), (c1, o2) .. (c3, o9)} na nowy typ, który utworzyłem tak samo jak w przykładzie.
Więc:
Powstała kolekcja jest płaska, jak można by się spodziewać po oryginalnym przykładzie.
Jeśli pominąłbyś drugi argument, skończyłbyś z kolekcją wszystkich zamówień pasujących do klienta. To byłaby po prostu płaska kolekcja
Order
przedmiotów.Używanie go wymaga dużo czasu, aby się do niego przyzwyczaić, ale czasami nadal mam problem z owinięciem głowy wokół niego. :(
źródło
GroupBy
może być lepszą opcją w tym konkretnym scenariuszu.SelectMany () działa jak Select, ale z dodatkową funkcją spłaszczania zaznaczonej kolekcji. Powinien być używany zawsze, gdy chcesz rzutować elementy kolekcji podrzędnych i nie przejmuj się tym, który zawiera elementy kolekcji podrzędnej.
Załóżmy na przykład, że Twoja domena wyglądała tak:
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; } }
Aby uzyskać tę samą listę, którą chciałeś, Twój Linq wyglądałby mniej więcej tak:
var customerOrders = Customers .SelectMany(c=>c.Orders) .Select(o=> new { CustomerId = o.Customer.Id, OrderDescription = o.Description });
... co da ten sam wynik bez konieczności zbierania płaskich zamówień. SelectMany pobiera kolekcję zamówień każdego klienta i iteruje przez nią, aby utworzyć
IEnumerable<Order>
plikIEnumerable<Customer>
.źródło
Chociaż jest to stare pytanie, pomyślałem, że poprawię trochę doskonałe odpowiedzi:
SelectMany zwraca listę (która może być pusta) dla każdego elementu listy kontrolnej. Każdy element na tych listach wyników jest wyliczany w sekwencji wyjściowej wyrażeń i dlatego jest łączony z wynikiem. Stąd 'lista -> b' lista [] -> konkatenacja -> b 'lista.
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); } } }
źródło
Oto kolejna opcja przy użyciu SelectMany
var customerOrders = customers.SelectMany( c => orders.Where(o => o.CustomerId == c.Id) .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
Jeśli używasz Entity Framework lub LINQ to Sql i masz skojarzenie (relację) między jednostkami, możesz to zrobić:
var customerOrders = customers.SelectMany( c => c.orders .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
źródło