根据我对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查询吗?
答案 0 :(得分:100)
以下是使用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)}转换为一个新类型,我的例子与你的例子相同。
所以:
生成的集合与原始示例中的预期相同。
如果您要省略第二个参数,您最终会收到与客户匹配的所有订单的集合。它只是一个Order
对象的扁平集合。
使用它需要大量的习惯,有时我仍然无法绕过它。 :(
答案 1 :(得分: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; }
}
要获得您想要的相同列表,您的Linq看起来像这样:
var customerOrders = Customers
.SelectMany(c=>c.Orders)
.Select(o=> new { CustomerId = o.Customer.Id,
OrderDescription = o.Description });
...这将产生相同的结果,而不需要订单的扁平集合。 SelectMany获取每个Customer's Orders集合并迭代它以从IEnumerable<Order>
生成IEnumerable<Customer>
。
答案 2 :(得分:5)
虽然这是一个老问题,但我想我会稍微改善一下这个优秀的答案:
SelectMany为控制列表的每个元素返回一个列表(可能为空)。这些结果列表中的每个元素都枚举到表达式的输出序列中,因此连接到结果中。因此,'列表 - &gt; b'list [] - &gt;连接 - &gt; 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);
}
}
}
答案 3 :(得分:0)
这是使用SelectMany的另一个选项
var customerOrders = customers.SelectMany(
c => c.orders
.Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
如果您使用Entity Framework或LINQ to Sql并且实体之间存在关联(关系),那么您可以这样做:
{{1}}