Linq-SelectMany 혼란


81

내가 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 쿼리를 제공 할 수 있습니까?


3
Jon Skeet의 Edulinq 시리즈 41 부 참조 . 쿼리 표현식 번역 프로세스를 설명합니다.
R. Martinho Fernandes

2
그것에 대해 생각도 참조 SelectMany : 9 부 :
R. 마르틴 페르난데스

3
John Skeet의 Edulinq 시리즈는 이제 여기에서 사용할 수 있습니다 .
Dan Jagnow

답변:


101

다음은을 사용하는 쿼리 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물건의 집합 일 것입니다 .

익숙해지는 데 많은 시간이 걸리지 만 가끔 머리를 감는 데 어려움이 있습니다. :(


2
귀하의 답변과 설명에 감사드립니다. 그게 바로 제가 필요했던 것입니다. 제 질문의 맥락에서 완전히 답을 제공 해주셔서 감사합니다. 훨씬 쉽게 이해할 수 있습니다.
Jackie Kirby

1
Pete를 위해 왜 .Where ()를 SelectMany () 안에 넣었을까요? 지적 해 주셔서 감사합니다 ...
Tobias J

기록 GroupBy을 위해이 특정 시나리오에 더 나은 옵션이 될 수 있습니다.
Ekevoo

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


3
"(...) 하위 컬렉션의 포함 요소는 신경 쓰지 마세요." 평면화를 원하고 포함하는 요소에 대해 신경 쓰는 경우 SelectMany과부하 가 있습니다. :)
R. Martinho Fernandes

@Keith 귀하의 답변에 감사드립니다. 플랫 주문 컬렉션에 어떻게 사용합니까?
Jackie Kirby

도메인이 약간 의심스러워 보입니다. 주문에 많은 주문이 포함 된 고객이 있습니까?
Buh Buh

@Buh Buh, 주문에 고객이 아닌 CustomerId가 포함되어 있지 않습니다.
Jackie Kirby

1
@Buh Buh-나는 이것을 여러 번보고 해왔다. 따라서 하향식뿐만 아니라 모든 방향으로 이동할 수있는 개체 그래프가 생성됩니다. 그래프에 여러 "진입 지점"이있는 경우 매우 유용합니다. NHibernate와 같은 ORM을 사용하는 경우 하위 테이블에 이미 존재하므로 역 참조를 포함하는 것은 간단합니다. 캐스케이드가 위로가 아니라 아래로 내려 간다고 말함으로써 순환 참조를 깨기 만하면됩니다.
KeithS

5

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

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

1

다음은 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}));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.