Linq - SelectMany Confusion
Из того, что я понимаю из документации 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?
Ответы
Ответ 1
Вот ваш запрос, используя SelectMany
, смоделированный именно после вашего примера. Тот же выход!
var customerOrders2 = customers.SelectMany(
c => orders.Where(o => o.CustomerId == c.Id),
(c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });
Первый аргумент отображает каждого клиента в набор заказов (полностью аналогичный тому, что вы уже имеете),
Второй аргумент преобразует каждую согласованную пару {(c1, o1), (c1, o2).. (c3, o9)} в новый тип, который я сделал так же, как ваш пример.
Итак:
- arg1 сопоставляет каждый элемент в базовой коллекции другой коллекции.
- arg2 (необязательно) преобразует каждую пару в новый тип
Полученная коллекция плоская, как вы ожидали в своем исходном примере.
Если бы вы опустили второй аргумент, у вас получилось бы собрание всех заказов, совпадающих с клиентом. Это будет просто плоская коллекция объектов Order
.
Использование этого требует многого, чтобы привыкнуть, у меня все еще есть проблемы, обматывающие его вокруг него иногда.: (
Ответ 2
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>
.
Ответ 3
Хотя это старый вопрос, я подумал, что немного улучшу превосходные ответы:
SelectMany возвращает список (который может быть пустым) для каждого элемента контрольного списка. Каждый элемент в этих списках результатов перечисляется в выходную последовательность выражений и поэтому объединяется в результат. Следовательно, список "list → b" [] → concatenate → 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);
}
}
}
Ответ 4
Вот еще один вариант с помощью 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}));