Linq-SelectMany Путаница
из того, что я понимаю из документации SelectMany, можно было бы использовать его для создания (сглаженной) последовательности отношения 1-many.
у меня есть следующие классы
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?
4 ответа:
вот ваш запрос с помощью
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)} в новый тип, который я сделал так же, как и ваш пример.
Так:
- arg1 отображает каждый элемент в базе коллекции в другую коллекцию.
- arg2 (необязательно) преобразует каждую пару в новый тип
полученная коллекция плоская, как и следовало ожидать в исходном примере.
Если вы опустите второй аргумент, вы получите коллекцию всех заказов, совпадающих с клиентом. Это было бы просто, плоская коллекция
Order
объекты.использование этого требует много привыкания, у меня все еще есть проблемы с обертыванием головы иногда вокруг него. : (
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}));