[c#] Linq-SelectMany 혼란

내가 SelectMany의 문서에서 이해 한 바에 따르면,이를 사용하여 일대 다 관계의 (평탄화 된) 시퀀스를 생성 할 수 있습니다.

다음 수업이 있습니다

  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입니다. 동일한 출력!

        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 ()는 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는 각 고객의 주문 컬렉션을 가져와이를 반복 IEnumerable<Order>하여 IEnumerable<Customer>.


답변

이것은 오래된 질문이지만 우수한 답변을 조금 향상시킬 것이라고 생각했습니다.

SelectMany는 제어 목록의 각 요소에 대해 목록 (비어있을 수 있음)을 반환합니다. 이러한 결과 목록의 각 요소는 식의 출력 시퀀스에 열거되므로 결과에 연결됩니다. 따라서 a ‘list-> b’list []-> concatenate-> b ‘list.

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


답변

다음은 SelectMany를 사용하는 또 다른 옵션입니다.

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

Entity Framework 또는 LINQ to Sql을 사용하고 엔터티간에 연결 (관계)이있는 경우 다음을 수행 할 수 있습니다.

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


답변