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 में अनुवाद नहीं करती है, तो क्या कोई मुझे Linq क्वेरी प्रदान कर सकता है जो SelectMany का उपयोग करता है?


3
जॉन स्कीट के एडुलिनक श्रृंखला का भाग 41 देखें । यह क्वेरी एक्सप्रेशन ट्रांसलेशन प्रक्रिया को समझाता है।
आर। मार्टिनो फर्नांडिस

2
इसके बारे में सोचते हुए, भाग 9 भी देखें : SelectMany :)
आर। मार्टिनो फर्नांडीस

3
जॉन स्कीट की एडुलिनक श्रृंखला अब यहां उपलब्ध है
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 });

पहला तर्क प्रत्येक ग्राहक को आदेशों के संग्रह के लिए मैप करता है (पूरी तरह से 'जहाँ' आपके पास पहले से मौजूद है) के प्रति विनम्र।

दूसरा तर्क प्रत्येक मिलान जोड़ी {(c1, o1), (c1, o2) .. (c3, o9)} को एक नए प्रकार में बदल देता है, जिसे मैंने आपके उदाहरण के समान बनाया है।

इसलिए:

  • arg1 आधार संग्रह में प्रत्येक तत्व को दूसरे संग्रह में मैप करता है।
  • arg2 (वैकल्पिक) प्रत्येक जोड़ी को एक नए प्रकार में बदल देता है

परिणामी संग्रह समतल है जैसे आप अपने मूल उदाहरण में अपेक्षा करेंगे।

यदि आप दूसरे तर्क को छोड़ना चाहते थे, तो आप एक ग्राहक के लिए सभी आदेशों के संग्रह के साथ समाप्त होंगे। यह सिर्फ वह होगा, Orderवस्तुओं का एक फ्लैट संग्रह ।

इसका उपयोग करने के लिए बहुत अधिक उपयोग हो रहा है, मुझे अभी भी इसके चारों ओर अपना सिर लपेटने में परेशानी होती है। :(


2
आपके उत्तर और स्पष्टीकरण के लिए धन्यवाद। ठीक वैसा ही मुझे चाहिए। मेरे प्रश्न के संदर्भ में पूरी तरह से उत्तर प्रदान करने के लिए भी धन्यवाद, इससे यह समझने में बहुत आसान हो जाता है।
जैकी किर्बी

1
पीट की खातिर, SelectMany के अंदर .Where () को क्यों रखा गया () मुझे इतने समय के लिए हटा दें ?? उस ओर इशारा करने के लिए धन्यवाद ...
टोबियास जे

सिर्फ रिकॉर्ड के लिए, GroupByइस विशेष परिदृश्य के लिए एक बेहतर विकल्प हो सकता है।
एकेवू

27

SelectMany () सेलेक्ट की तरह काम करता है, लेकिन एक संग्रह को चपटा करने की उस अतिरिक्त विशेषता के साथ जिसे चुना जाता है। जब भी आप उप-संग्रह के तत्वों का प्रक्षेपण चाहते हैं, तो इसका उपयोग किया जाना चाहिए, और उप-संग्रह वाले तत्व के बारे में परवाह न करें।

उदाहरण के लिए, मान लें कि आपका डोमेन इस तरह दिखता है:

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.

@ कीथ आपके उत्तर के लिए धन्यवाद। आदेशों के एक फ्लैट संग्रह के साथ मैं इसका उपयोग कैसे करूंगा?
जैकी किर्बी

आपका डोमेन थोड़ा संदिग्ध लगता है। एक ऑर्डर में एक ग्राहक होता है जो बदले में कई आदेश देता है
बुह बुह

@ बुउ बुह, नो ऑर्डर में एक ग्राहक नहीं एक ग्राहक शामिल है।
जैकी किर्बी

1
@ बुउ बुह - मैंने कई बार यह देखा और किया है; यह एक वस्तु ग्राफ में परिणाम करता है जिसे किसी भी दिशा में ट्रेस किया जा सकता है, न कि केवल टॉप-डाउन। बहुत उपयोगी है अगर आपके ग्राफ में "प्रविष्टि के कई बिंदु" हैं। यदि आप NHibernate जैसे एक ORM का उपयोग करते हैं, तो बैकरेफेरेंस को शामिल करना तुच्छ है क्योंकि यह पहले से ही बच्चे की तालिका में मौजूद है। आपको सिर्फ यह बताते हुए परिपत्र संदर्भ को तोड़ना होगा कि कैस्केड नीचे जाते हैं, ऊपर नहीं।
कीथ

5

हालांकि यह एक पुराना सवाल है, मैंने सोचा कि मैं उत्कृष्ट उत्तरों को थोड़ा सुधार दूंगा:

नियंत्रण सूची के प्रत्येक तत्व के लिए SelectMany एक सूची (जो खाली हो सकती है) देता है। इन परिणाम सूचियों में प्रत्येक तत्व को अभिव्यक्ति के आउटपुट अनुक्रम में शामिल किया गया है और इसलिए परिणाम में संक्षिप्त किया गया है। इसलिए, एक 'सूची -> बी' सूची [] -> संक्षिप्त -> बी सूची।

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

यदि आप Sql में Entity Framework या LINQ का उपयोग करते हैं और संस्थाओं के बीच आपका संबंध (संबंध) है, तो आप ऐसा कर सकते हैं:

var customerOrders = customers.SelectMany(
  c => c.orders
   .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.