Linq - SelectMany Confusion

Jac*_*rby 81 c# linq linq-to-objects data-structures

根据我从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; }
  }
Run Code Online (Sandbox Code Playgroud)

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

  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);
Run Code Online (Sandbox Code Playgroud)

这给了我需要的东西.

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
Run Code Online (Sandbox Code Playgroud)

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

无论哪种方式,我都试图用SelectMany来解决问题.因此,即使我的上述查询没有转换为SelectMany,给定两个类和模拟数据,有人可以为我提供使用SelectMany的linq查询吗?

Sap*_*pph 101

这是您的查询使用SelectMany,完全按照您的示例建模.相同的输出!

        var customerOrders2 = customers.SelectMany(
            c => orders.Where(o => o.CustomerId == c.Id),
            (c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });
Run Code Online (Sandbox Code Playgroud)

第一个参数将每个客户映射到一个订单集合(与您已经拥有的'where'子句完全相似).

第二个参数将每个匹配的对{(c1,o1),(c1,o2)..(c3,o9)}转换为一个新类型,我和你的例子一样.

所以:

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

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

如果您要省略第二个参数,那么最终会得到与客户匹配的所有订单的集合.它只是一个平面的Order物体集合.

使用它需要大量的习惯,有时候我仍然无法绕过它.:(

  • 谢谢你的回答和解释.这正是我所需要的.还要感谢您在我的问题中完全提供答案,这使得它更容易理解. (2认同)

Kei*_*thS 27

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; }
  }
Run Code Online (Sandbox Code Playgroud)

要获得您想要的相同列表,您的Linq看起来像这样:

var customerOrders = Customers
                        .SelectMany(c=>c.Orders)
                        .Select(o=> new { CustomerId = o.Customer.Id, 
                                           OrderDescription = o.Description });
Run Code Online (Sandbox Code Playgroud)

...这将产生相同的结果,而不需要平面的订单集合.SelectMany获取每个Customer's Orders集合并迭代它以生成一个IEnumerable<Order>from IEnumerable<Customer>.

  • *"(...)并且不关心子集合的包含元素."*如果你想要展平,并且你关心包含元素,那么有一个[SelectMany的重载](http:// msdn.microsoft.com/en-us/library/bb534631.aspx):) (3认同)

Geo*_*rge 5

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

SelectMany为控制列表的每个元素返回一个列表(可能为空).这些结果列表中的每个元素都枚举到表达式的输出序列中,因此连接到结果中.因此,'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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)