Насколько я понимаю из документации SelectMany, можно было бы использовать его для создания (сглаженной) последовательности отношения 1-многие.
У меня следующие классы
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; }
}
Затем я пытаюсь использовать их, используя синтаксис выражения запроса, например
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);
Это дает то, что мне нужно.
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
Я предполагаю, что это означает использование метода SelectMany, когда не используется синтаксис выражения запроса?
В любом случае, я пытаюсь обойтись с помощью SelectMany. Итак, даже если мой вышеупомянутый запрос не переводится в SelectMany, учитывая два класса и фиктивные данные, может ли кто-нибудь предоставить мне запрос linq, который использует SelectMany?
c#
linq
data-structures
linq-to-objects
Джеки Кирби
источник
источник
Ответы:
Вот ваш запрос с использованием
SelectMany
, смоделированный точно по вашему примеру. Такой же результат!var customerOrders2 = customers.SelectMany( c => orders.Where(o => o.CustomerId == c.Id), (c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });
Первый аргумент сопоставляет каждого покупателя с набором заказов (полностью аналогично предложению where, которое у вас уже есть).
Второй аргумент преобразует каждую согласованную пару {(c1, o1), (c1, o2) .. (c3, o9)} в новый тип, который я сделал так же, как ваш пример.
Так:
Полученная коллекция плоская, как и следовало ожидать в исходном примере.
Если бы вы пропустили второй аргумент, вы бы получили совокупность всех заказов, соответствующих покупателю. Это будет просто набор плоских
Order
предметов.К его использованию нужно привыкнуть, иногда мне все еще трудно обернуть голову вокруг него. :(
источник
GroupBy
может быть лучшим вариантом для этого конкретного сценария.SelectMany () работает как Select, но с дополнительной функцией выравнивания выбранной коллекции. Его следует использовать всякий раз, когда вам нужна проекция элементов вложенных коллекций, и вы не заботитесь о элементе, содержащем вложенную коллекцию.
Например, предположим, что ваш домен выглядел так:
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; } }
Чтобы получить тот же список, который вам нужен, ваш Linq будет выглядеть примерно так:
var customerOrders = Customers .SelectMany(c=>c.Orders) .Select(o=> new { CustomerId = o.Customer.Id, OrderDescription = o.Description });
... который даст тот же результат без необходимости плоского сбора заказов. SelectMany берет каждую коллекцию заказов клиентов и перебирает ее, чтобы создать
IEnumerable<Order>
из файлаIEnumerable<Customer>
.источник
Хотя это старый вопрос, я подумал, что немного улучшу отличные ответы:
SelectMany возвращает список (который может быть пустым) для каждого элемента контрольного списка. Каждый элемент в этих списках результатов перечисляется в выходной последовательности выражений и, таким образом, объединяется в результат. Следовательно, список '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); } } }
источник
Вот еще один вариант с использованием SelectMany
var customerOrders = customers.SelectMany( c => orders.Where(o => o.CustomerId == c.Id) .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
Если вы используете Entity Framework или LINQ to Sql и у вас есть связь (связь) между сущностями, вы можете сделать это:
var customerOrders = customers.SelectMany( c => c.orders .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
источник