आप linq एक्सटेंशन मेथड्स का उपयोग करके लेफ्ट ऑउट जॉइन कैसे करते हैं


272

यह मानते हुए कि मेरे पास एक बाहरी बाहरी भाग है:

from f in Foo
join b in Bar on f.Foo_Id equals b.Foo_Id into g
from result in g.DefaultIfEmpty()
select new { Foo = f, Bar = result }

मैं एक्सटेंशन विधियों का उपयोग करके समान कार्य कैसे व्यक्त करूंगा? उदाहरण के लिए

Foo.GroupJoin(Bar, f => f.Foo_Id, b => b.Foo_Id, (f,b) => ???)
    .Select(???)

जवाबों:


444

एक (बाहरी छोड़ दिया) एक तालिका के शामिल होने के लिए Barएक मेज के साथ Fooपर Foo.Foo_Id = Bar.Foo_Idलैम्ब्डा अंकन में:

var qry = Foo.GroupJoin(
          Bar, 
          foo => foo.Foo_Id,
          bar => bar.Foo_Id,
          (x,y) => new { Foo = x, Bars = y })
       .SelectMany(
           x => x.Bars.DefaultIfEmpty(),
           (x,y) => new { Foo=x.Foo, Bar=y});

27
यह वास्तव में लगभग उतना पागल नहीं है जितना लगता है। मूल रूप GroupJoinसे बायाँ बाहरी SelectManyभाग जुड़ता है, केवल उस भाग की आवश्यकता होती है जो आप चयन करना चाहते हैं।
जॉर्ज मौअर

6
यह पैटर्न बहुत अच्छा है क्योंकि एंटिटी फ्रेमवर्क इसे एक लेफ्ट जॉइन के रूप में पहचानता है, जिसे मैं मानता था कि यह असंभव है
जेसन फाफॉन

3
@ अच्छी तरह से आपको एक कथन की आवश्यकता होगी, x.Bar == अशक्त
टॉड

2
@AbdulkarimKanaan हाँ - SelectMany प्रति जोड़ी एक प्रवेश के साथ 1 परत में 1-कई की दो परतों सपाट
मार्क Gravell

1
@MarcGravell मैंने अपने कोड फ़्रिज़न में आपके द्वारा किए गए स्पष्टीकरण का एक सा जोड़ने के लिए एक संपादन का सुझाव दिया।
बी - रियान

109

चूंकि यह विधि (विस्तार) सिंटैक्स का उपयोग करते हुए बाएं बाहरी जोड़ के लिए वास्तविक तथ्य है, इसलिए मैंने सोचा कि मैं वर्तमान में चयनित उत्तर के लिए एक विकल्प जोड़ दूंगा (कम से कम मेरे अनुभव में) आमतौर पर मैं क्या कर रहा हूं उपरांत

// Option 1: Expecting either 0 or 1 matches from the "Right"
// table (Bars in this case):
var qry = Foos.GroupJoin(
          Bars,
          foo => foo.Foo_Id,
          bar => bar.Foo_Id,
          (f,bs) => new { Foo = f, Bar = bs.SingleOrDefault() });

// Option 2: Expecting either 0 or more matches from the "Right" table
// (courtesy of currently selected answer):
var qry = Foos.GroupJoin(
                  Bars, 
                  foo => foo.Foo_Id,
                  bar => bar.Foo_Id,
                  (f,bs) => new { Foo = f, Bars = bs })
              .SelectMany(
                  fooBars => fooBars.Bars.DefaultIfEmpty(),
                  (x,y) => new { Foo = x.Foo, Bar = y });

एक साधारण डेटा सेट का उपयोग करके अंतर प्रदर्शित करने के लिए (यह मानकर कि हम स्वयं मूल्यों में शामिल हो रहे हैं):

List<int> tableA = new List<int> { 1, 2, 3 };
List<int?> tableB = new List<int?> { 3, 4, 5 };

// Result using both Option 1 and 2. Option 1 would be a better choice
// if we didn't expect multiple matches in tableB.
{ A = 1, B = null }
{ A = 2, B = null }
{ A = 3, B = 3    }

List<int> tableA = new List<int> { 1, 2, 3 };
List<int?> tableB = new List<int?> { 3, 3, 4 };

// Result using Option 1 would be that an exception gets thrown on
// SingleOrDefault(), but if we use FirstOrDefault() instead to illustrate:
{ A = 1, B = null }
{ A = 2, B = null }
{ A = 3, B = 3    } // Misleading, we had multiple matches.
                    // Which 3 should get selected (not arbitrarily the first)?.

// Result using Option 2:
{ A = 1, B = null }
{ A = 2, B = null }
{ A = 3, B = 3    }
{ A = 3, B = 3    }    

विकल्प 2 ठेठ बाईं बाहरी जुड़ाव परिभाषा के लिए सही है, लेकिन जैसा कि मैंने पहले उल्लेख किया है कि डेटा सेट के आधार पर अक्सर अनावश्यक रूप से जटिल होता है।


7
मुझे लगता है कि "bs.SingleOrDefault ()" काम नहीं करेगा यदि आपके पास कोई अन्य जॉइन या सम्मिलित है। हमें इस मामलों में "bs.FirstOrDefault ()" की आवश्यकता है।
धेरिक

3
सही, एंटिटी फ्रेमवर्क और लाइनक टू एसक्यूएल दोनों की आवश्यकता होती है क्योंकि वे आसानी से Singleचेक में शामिल नहीं हो सकते हैं । SingleOrDefaultहालाँकि इस IMO को प्रदर्शित करने का एक अधिक "सही" तरीका है।
Ocelot20

1
आपको अपनी सम्मिलित तालिका को ऑर्डर करने के लिए याद रखने की आवश्यकता है या .FirstOrDefault () कई पंक्तियों से एक यादृच्छिक पंक्ति प्राप्त करने जा रहा है जो कि शामिल होने के मानदंडों से मेल खा सकता है, जो भी डेटाबेस को खोजने के लिए होता है।
बजे क्रिस मोसची

1
@ क्रिसमोसचिनी: ऑर्डर और फर्स्टऑडफॉल्ट अनावश्यक हैं क्योंकि उदाहरण 0 या 1 मैच के लिए है जहां आप कई रिकॉर्ड्स में विफल होना चाहते हैं (कोड के ऊपर टिप्पणी देखें)।
Ocelot20

2
यह प्रश्न में "अतिरिक्त आवश्यकता" अनिर्दिष्ट नहीं है, यह बहुत से लोग सोचते हैं कि वे "लेफ्ट आउटर जॉइन" कब कहते हैं। इसके अलावा, Dherik द्वारा संदर्भित FirstOrDefault आवश्यकता EF / L2SQL व्यवहार है और L2Objects (इनमें से कोई भी टैग में नहीं हैं)। SingleOrDefault इस मामले में कॉल करने के लिए बिल्कुल सही तरीका है। बेशक आप एक अपवाद फेंकना चाहते हैं यदि आप अपने डेटा सेट के लिए एक से अधिक रिकॉर्ड करने के बजाय एक मनमाना और एक भ्रामक अपरिभाषित परिणाम की ओर अग्रसर हों।
Ocelot20

52

दो डेटा सेट में शामिल होने को प्राप्त करने के लिए ग्रुप जॉइन विधि अनावश्यक है।

आंतरिक रूप से जुड़ा:

var qry = Foos.SelectMany
            (
                foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id),
                (foo, bar) => new
                    {
                    Foo = foo,
                    Bar = bar
                    }
            );

लेफ्ट जॉइन के लिए सिर्फ DefaultIfEmpty () जोड़ें

var qry = Foos.SelectMany
            (
                foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id).DefaultIfEmpty(),
                (foo, bar) => new
                    {
                    Foo = foo,
                    Bar = bar
                    }
            );

EF और LINQ SQL को सही ढंग से SQL में रूपांतरित करता है। LINQ ऑब्जेक्ट्स के लिए यह GroupJoin का उपयोग करके जुड़ने के लिए दांव पर है क्योंकि यह आंतरिक रूप से लुकअप का उपयोग करता है । लेकिन अगर आप DB को क्वेरी कर रहे हैं तो GroupJoin की स्किपिंग एएफएआईके के रूप में है।

मेरे लिए यह तरीका GroupJoin () की तुलना में अधिक पठनीय है। SelectMany ()


यह एक सुगंधित से बेहतर है। मेरे लिए, साथ ही मैं अपना संघात्मक संयुक्त काम कर सकता हूं जो मैं चाहता था (दायाँ। FooId == left.FooId || right.FooId == 0)
Anders

linq2sql बाईं ओर जुड़ने के रूप में इस दृष्टिकोण का अनुवाद करता है। यह उत्तर बेहतर और सरल है। +1
गुइडो मोचा

15

आप विस्तार विधि बना सकते हैं जैसे:

public static IEnumerable<TResult> LeftOuterJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> other, Func<TSource, TKey> func, Func<TInner, TKey> innerkey, Func<TSource, TInner, TResult> res)
    {
        return from f in source
               join b in other on func.Invoke(f) equals innerkey.Invoke(b) into g
               from result in g.DefaultIfEmpty()
               select res.Invoke(f, result);
    }

ऐसा लगता है कि यह काम करेगा (मेरी आवश्यकता के लिए)। क्या आप एक उदाहरण प्रदान कर सकते हैं? मैं LINQ एक्सटेंशन में नया हूं और इस लेफ्ट के आसपास मेरे सिर को लपेटने में मुश्किल समय आ रहा है, मैं इस स्थिति में शामिल हो गया हूं ...
शिव

@ साइकिशन हो सकता है कि मुझे इसे देखने की जरूरत है, यह पुराना उत्तर है और उस समय काम कर रहा था। आप किस फ्रेमवर्क का उपयोग कर रहे हैं? मेरा मतलब है .NET संस्करण?
हजिराज़िन

2
यह Linq के लिए वस्तुओं के लिए काम करता है, लेकिन डेटाबेस को क्वेरी करते समय नहीं, क्योंकि आपको IQuerable पर काम करने की आवश्यकता होती है और इसके बजाय फ़ंक्शंस का उपयोग करना होता है
Bob Vale

4

Ocelot20 के उत्तर में सुधार करते हुए, यदि आपके पास एक मेज है, तो आप बाहरी रूप से जुड़ रहे हैं, जहाँ आप इसके साथ 0 या 1 पंक्तियाँ चाहते हैं, लेकिन इसमें कई हो सकते हैं, आपको अपनी सम्मिलित तालिका को क्रमबद्ध करने की आवश्यकता है:

var qry = Foos.GroupJoin(
      Bars.OrderByDescending(b => b.Id),
      foo => foo.Foo_Id,
      bar => bar.Foo_Id,
      (f, bs) => new { Foo = f, Bar = bs.FirstOrDefault() });

अन्यथा जो भी पंक्ति आपको जुड़ने में मिलती है वह यादृच्छिक (या अधिक विशेष रूप से, जो भी db पहले खोजने के लिए होती है) होने वाली है।


बस! किसी भी एक से एक संबंध के लिए किसी भी तरह का तर्क।
it3xl

2

मार्क ग्रेवेल के उत्तर को विस्तार विधि में बदलकर, मैंने निम्नलिखित बनाया।

internal static IEnumerable<Tuple<TLeft, TRight>> LeftJoin<TLeft, TRight, TKey>(
    this IEnumerable<TLeft> left,
    IEnumerable<TRight> right,
    Func<TLeft, TKey> selectKeyLeft,
    Func<TRight, TKey> selectKeyRight,
    TRight defaultRight = default(TRight),
    IEqualityComparer<TKey> cmp = null)
{
    return left.GroupJoin(
            right,
            selectKeyLeft,
            selectKeyRight,
            (x, y) => new Tuple<TLeft, IEnumerable<TRight>>(x, y),
            cmp ?? EqualityComparer<TKey>.Default)
        .SelectMany(
            x => x.Item2.DefaultIfEmpty(defaultRight),
            (x, y) => new Tuple<TLeft, TRight>(x.Item1, y));
}

2

जबकि स्वीकृत जवाब काम करता है और Linq के लिए अच्छा है कि यह वस्तुओं के लिए मुझे यह समझ में आया कि SQL क्वेरी सिर्फ एक सीधे बाएँ बाहरी जोड़ नहीं है।

निम्न कोड LinkKit प्रोजेक्ट पर निर्भर करता है जो आपको भावों को पास करने और उन्हें आपकी क्वेरी में लागू करने की अनुमति देता है।

static IQueryable<TResult> LeftOuterJoin<TSource,TInner, TKey, TResult>(
     this IQueryable<TSource> source, 
     IQueryable<TInner> inner, 
     Expression<Func<TSource,TKey>> sourceKey, 
     Expression<Func<TInner,TKey>> innerKey, 
     Expression<Func<TSource, TInner, TResult>> result
    ) {
    return from a in source.AsExpandable()
            join b in inner on sourceKey.Invoke(a) equals innerKey.Invoke(b) into c
            from d in c.DefaultIfEmpty()
            select result.Invoke(a,d);
}

इसका उपयोग निम्नानुसार किया जा सकता है

Table1.LeftOuterJoin(Table2, x => x.Key1, x => x.Key2, (x,y) => new { x,y});

-1

इसका एक आसान उपाय है

बस अपने चयन में .HasValue का उपयोग करें

.Select(s => new 
{
    FooName = s.Foo_Id.HasValue ? s.Foo.Name : "Default Value"
}

बहुत आसान है, Groupjoin या कुछ और के लिए कोई ज़रूरत नहीं है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.