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; } } 

然后我尝试使用查询expression式语法来使用它们

  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 

我假设这转换为在不使用查询expression式语法时使用SelectMany方法?

无论哪种方式,我试图围绕使用SelectMany我的头。 所以,即使我上面的查询不转换为SelectMany,给定两个类和模拟数据,有人可以提供一个使用SelectMany的linq查询?

这里是你的查询使用SelectMany ,完全按照你的例子build模。 相同的输出!

  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)}变换成一个新的types,这与我的例子相同。

所以:

  • arg1将基本集合中的每个元素映射到另一个集合。
  • arg2(可选)将每一对转换为一个新types

最终的集合就像您在原始示例中所期望的那样是平坦的。

如果您要省略第二个参数,那么您最终会收集所有与客户匹配的订单。 就是这样,一个Order对象的平面集合。

使用它需要很多习惯,有时候我还是很困惑。 🙁

SelectMany()的工作方式与Select相似,但具有展平所选集合的额外function。 它应该用于任何时候你想投影子集合的元素,而不关心子集合的包含元素。

例如,假设您的域名如下所示:

 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接受每个Customer的Orders集合,并通过它迭代,从IEnumerable<Customer>生成一个IEnumerable<Order> IEnumerable<Customer>

虽然这是一个古老的问题,但我想我会改善一些优秀的答案:

SelectMany为控制列表的每个元素返回一个列表(可能为空)。 这些结果列表中的每个元素都被枚举到expression式的输出序列中,因此被连接到结果中。 因此,一个'list – > b'列表[] – >连接 – > 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); } } } 

这是使用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}));