Linq - SelectMany Confusion


81

Từ những gì tôi hiểu từ tài liệu của SelectMany, người ta có thể sử dụng nó để tạo ra một chuỗi (phẳng) của mối quan hệ 1-nhiều.

Tôi có các lớp học sau

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

Sau đó, tôi cố gắng sử dụng chúng bằng cách sử dụng cú pháp biểu thức truy vấn như vậy

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

Điều này mang lại cho những gì tôi cần.

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

Tôi giả sử điều này chuyển sang sử dụng phương thức SelectMany khi không sử dụng cú pháp biểu thức truy vấn?

Dù bằng cách nào, tôi đang cố gắng sử dụng SelectMany. Vì vậy, ngay cả khi truy vấn ở trên của tôi không dịch sang SelectMany, với hai lớp và dữ liệu giả, ai đó có thể cung cấp cho tôi truy vấn linq sử dụng SelectMany không?


3
Đón xem phần 41 của loạt phim Edulinq của Jon Skeet . Nó giải thích quá trình dịch biểu thức truy vấn.
R. Martinho Fernandes

2
Suy nghĩ về điều đó, hãy xem thêm Phần 9: SelectMany :)
R. Martinho Fernandes

3
Bộ truyện Edulinq của John Skeet hiện đã có tại đây .
Dan Jagnow

Câu trả lời:


101

Đây là truy vấn của bạn đang sử dụng SelectMany, được mô phỏng chính xác theo ví dụ của bạn. Cùng một đầu ra!

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

Đối số đầu tiên ánh xạ mỗi khách hàng đến một tập hợp các đơn đặt hàng (hoàn toàn giống với mệnh đề 'where' mà bạn đã có).

Đối số thứ hai biến đổi từng cặp đã so khớp {(c1, o1), (c1, o2) .. (c3, o9)} thành một kiểu mới, mà tôi đã thực hiện giống như ví dụ của bạn.

Vì thế:

  • arg1 ánh xạ từng phần tử trong tập hợp cơ sở sang tập hợp khác.
  • arg2 (tùy chọn) biến đổi mỗi cặp thành một kiểu mới

Bộ sưu tập kết quả là phẳng như bạn mong đợi trong ví dụ ban đầu của bạn.

Nếu bạn bỏ qua đối số thứ hai, bạn sẽ kết thúc với một tập hợp tất cả các đơn đặt hàng khớp với một khách hàng. Nó sẽ chỉ là, một tập hợp phẳng của Ordercác đối tượng.

Sử dụng nó mất nhiều thời gian để làm quen, tôi vẫn còn khó khăn khi quấn quanh đầu tôi đôi khi. :(


2
Cảm ơn câu trả lời và lời giải thích của bạn. Đó chính xác là những gì tôi cần. Cảm ơn bạn cũng đã cung cấp một câu trả lời hoàn toàn phù hợp với câu hỏi của tôi, nó làm cho nó dễ hiểu hơn nhiều.
Jackie Kirby

1
Vì lợi ích của Pete, tại sao việc đặt .Where () bên trong SelectMany () lại lẩn tránh tôi quá lâu ?? Cảm ơn đã chỉ ra rằng ...
Tobias J

Chỉ cho bản ghi, GroupBycó thể là một lựa chọn tốt hơn cho tình huống cụ thể này.
Ekevoo

27

SelectMany () hoạt động giống như Select, nhưng với tính năng bổ sung đó là làm phẳng một bộ sưu tập được chọn. Nó nên được sử dụng bất cứ khi nào bạn muốn chiếu các phần tử của tập hợp con và không quan tâm đến phần tử chứa của tập hợp con.

Ví dụ: giả sử miền của bạn trông giống như sau:

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

Để có được cùng một danh sách bạn muốn, Linq của bạn sẽ trông giống như sau:

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

... mà sẽ tạo ra cùng một kết quả mà không cần tập hợp các Đơn hàng. SelectMany nhận bộ sưu tập Đơn đặt hàng của từng Khách hàng và lặp lại thông qua đó để tạo ra IEnumerable<Order>từ một IEnumerable<Customer>.


3
"(...) và không quan tâm đến phần tử chứa của tập hợp con." Nếu bạn muốn sự làm phẳng, và bạn làm chăm sóc về các yếu tố có chứa, có một tình trạng quá tải của SelectMany cho :) rằng
R. Martinho Fernandes

@Keith cảm ơn vì câu trả lời của bạn. Làm thế nào tôi sẽ sử dụng nó với một bộ sưu tập đơn đặt hàng cố định?
Jackie Kirby

Miền của bạn có vẻ hơi đáng ngờ. Một Đơn hàng chứa một Khách hàng mà đến lượt nó lại chứa nhiều Đơn hàng?
Buh Buh

@Buh Buh, không có Đơn đặt hàng nào chứa CustomerId không phải là Khách hàng.
Jackie Kirby

1
@Buh Buh - Tôi đã thấy và làm điều này nhiều lần; nó dẫn đến một biểu đồ đối tượng có thể được duyệt theo bất kỳ hướng nào, không chỉ từ trên xuống. Rất hữu ích nếu đồ thị của bạn có một số "điểm vào". Nếu bạn sử dụng ORM như NHibernate, việc đưa vào backreference là điều vô cùng đơn giản vì nó đã tồn tại trong bảng con. Bạn chỉ cần phá vỡ tham chiếu vòng tròn bằng cách nói rằng các tầng đi xuống chứ không phải lên.
KeithS

5

Mặc dù đây là một câu hỏi cũ, nhưng tôi nghĩ mình sẽ cải thiện những câu trả lời xuất sắc một chút:

SelectMany trả về một danh sách (có thể trống) cho mỗi phần tử của danh sách kiểm soát. Mỗi phần tử trong danh sách kết quả này được liệt kê vào chuỗi đầu ra của biểu thức và do đó được nối vào kết quả. Do đó, danh sách 'list -> b' [] -> nối -> 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);
        }
    }
}

1

Đây là một tùy chọn khác sử dụng SelectMany

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

Nếu bạn sử dụng Khung thực thể hoặc LINQ cho Sql và bạn có mối liên kết (mối quan hệ) giữa các thực thể, thì bạn có thể làm như vậy:

var customerOrders = customers.SelectMany(
  c => c.orders
   .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.