C# Linq-SELECT有很多困惑

C# Linq-SELECT有很多困惑,c#,linq,data-structures,linq-to-objects,C#,Linq,Data Structures,Linq To Objects,从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; } pub

从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,考虑到这两个类和模拟数据,是否有人能为我提供一个使用SelectMany的linq查询?

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; }
  }
var customerOrders = Customers
                        .SelectMany(c=>c.Orders)
                        .Select(o=> new { CustomerId = o.Customer.Id, 
                                           OrderDescription = o.Description });

。。。这将产生相同的结果,而无需统一收集订单。SelectMany获取每个客户的订单集合,并通过该集合进行迭代,从一个
IEnumerable
生成一个
IEnumerable
,以下是您使用
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为控制列表的每个元素返回一个列表(可能为空)。这些结果列表中的每个元素都被枚举到表达式的输出序列中,因此被连接到结果中。因此,一个“列表->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);
        }
    }
}
使用系统;
使用Microsoft.VisualStudio.TestTools.UnitTesting;
使用System.Linq;
使用系统诊断;
命名空间Nop.Plugin.Misc.WebServices.Test
{
[测试类]
公共类测试库
{
[测试方法]
公共void TestMethod1()
{//请参见TestExplorer-测试输出中的结果
var a=新的int[]{7,8};
var b=新整数[]
{12,23,343,6464,232,75676,213,1232,544,86,97867,43};
Func numberHasDigit=
(编号
,数字)=>
(number.ToString()包含(digit.ToString());
Debug.WriteLine(“未筛选:针对'a'的每个元素的'b'的所有元素”);
foreach(a.SelectMany中的var l(aa=>b))
调试写入线(l);
Debug.WriteLine(string.Empty);
Debug.WriteLine(“过滤:”+
“由‘a’元素过滤的‘a’的每个元素的‘b’的所有元素”);
foreach(a.SelectMany中的var l(aa=>b.Where(bb=>numberHasDigit(bb,aa)))
调试写入线(l);
}
}
}

这里是使用SelectMany的另一个选项

var customerOrders = customers.SelectMany(
  c => orders.Where(o => o.CustomerId == c.Id)
    .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
如果使用实体框架或LINQ to Sql,并且实体之间存在关联(关系),则可以执行以下操作:

var customerOrders = customers.SelectMany(
  c => c.orders
   .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));

请参见Jon Skeet的。它解释了查询表达式转换过程。考虑一下,请参见:)John Skeet的Edulinq系列现在可用。“(…)并且不关心子集合的包含元素。“如果您想要展开,并且确实关心包含元素,对此有一个答案:)@Keith谢谢你的回答。我如何将它用于订单的平面集合?您的域看起来有点可疑。一个订单包含一个客户,而该客户又包含许多订单?@Buh Buh,不,一个订单包含一个客户ID而不是一个客户。@Buh Buh-我已经见过并做过很多次了;它生成的对象图可以在任何方向上进行遍历,而不仅仅是自顶向下。如果图形有多个“入口点”,则非常有用。如果您使用像NHibernate这样的ORM,那么包含backreference就很简单了,因为它已经存在于子表中。你只要打破循环引用,说明瀑布是下降的,而不是上升的。谢谢你的回答和解释。这正是我需要的。还感谢您在我的问题中提供了一个完整的答案,这使我的问题更容易理解。看在皮特的份上,为什么把.Where()放在SelectMany()中让我逃避了这么久??感谢您指出……为了记录在案,
GroupBy
可能是这个特定场景的更好选择。