Linq-SelectManyの混乱


81

SelectManyのドキュメントから私が理解していることから、これを使用して1対多の関係の(フラット化された)シーケンスを生成できます。

以下のクラスがあります

  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に変換されない場合でも、2つのクラスとモックデータが与えられた場合、誰かがSelectManyを使用するlinqクエリを提供できますか?


3
JonSkeetEdulinqシリーズパート41を参照してください。クエリ式の変換プロセスについて説明します。
R. Martinho Fernandes

2
それについて考えると、パート9:SelectMany :)
R. Martinho Fernandes

3
JohnSkeetのEdulinqシリーズがここで利用可能になりました
ダンジャグノウ2014

回答:


101

これは、SelectMany例を正確にモデル化した、を使用したクエリです。同じ出力!

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

最初の引数は、各顧客を注文のコレクションにマップします(既に持っている「where」句に完全に類似しています)。

2番目の引数は、一致した各ペア{(c1、o1)、(c1、o2)..(c3、o9)}を新しい型に変換します。これは、例と同じように作成しました。

そう:

  • arg1は、基本コレクションの各要素を別のコレクションにマップします。
  • arg2(オプション)は、各ペアを新しいタイプに変換します

結果のコレクションは、元の例で期待するようにフラットです。

2番目の引数を省略すると、顧客に一致するすべての注文のコレクションになります。それはまさにそれであり、Orderオブジェクトのフラットなコレクションです。

それを使うのはかなり慣れが必要ですが、それでも時々頭を包むのに苦労します。:(


2
あなたの答えと説明をありがとう。それがまさに私が必要としていたものです。また、私の質問の文脈で完全に答えを提供してくれてありがとう、それはそれをはるかに理解しやすくします。
ジャッキーカービー

1
ピートのために、SelectMany()内に.Where()を置くと、なぜそんなに長い間私を逃してしまったのですか?そのアウトを指してくれてありがとう...
トビアスJ

念のためにGroupBy言っておきますが、この特定のシナリオにはより良いオプションかもしれません。
Ekevoo 2016年

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>ます。


3
「(...)そして、サブコレクションの要素を含むことは気にしないでください。」平坦化が必要で、含まれている要素を気にする場合は、SelectManyのオーバーロードがあります:)
R. Martinho Fernandes

@Keithあなたの答えに感謝します。注文のフラットコレクションでどのように使用しますか?
ジャッキーカービー

あなたのドメインは少し疑わしいようです。注文には顧客が含まれ、その顧客には多くの注文が含まれていますか?
Buh Buh 2011

@Buh Buh、注文に顧客ではなく顧客IDが含まれていません。
ジャッキーカービー

1
@ BuhBuh-私はこれを何度も見てきました。その結果、トップダウンだけでなく、任意の方向にトラバースできるオブジェクトグラフが作成されます。グラフに複数の「エントリポイント」がある場合に非常に便利です。NHibernateのようなORMを使用する場合、子テーブルにすでに存在するため、後方参照を含めるのは簡単です。カスケードが上ではなく下に行くことを述べることによって、循環参照を破る必要があります。
キースS 2011

5

これは古い質問ですが、私は優れた回答を少し改善すると思いました。

SelectManyは、制御リストの各要素のリスト(空の場合もあります)を返します。これらの結果リストの各要素は、式の出力シーケンスに列挙されるため、結果に連結されます。したがって、a 'リスト-> 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);
        }
    }
}

1

SelectManyを使用した別のオプションは次のとおりです

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

EntityFrameworkまたはLINQto 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.