C# Linq-SELECT有很多困惑
从SelectMany的文档中我了解到,可以使用它生成1-many关系的(扁平化)序列 我有以下课程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
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
可能是这个特定场景的更好选择。