LINQ - पूरा बाहरी जुड़ना


202

मेरे पास लोगों की आईडी और उनके पहले नाम की सूची है, और लोगों की आईडी और उनके उपनाम की सूची है। कुछ लोगों का पहला नाम नहीं होता है और कुछ का उपनाम नहीं होता है; मैं दो सूचियों पर एक पूर्ण बाहरी कार्य करना चाहूंगा।

तो निम्न सूची:

ID  FirstName
--  ---------
 1  John
 2  Sue

ID  LastName
--  --------
 1  Doe
 3  Smith

उत्पादन करना चाहिए:

ID  FirstName  LastName
--  ---------  --------
 1  John       Doe
 2  Sue
 3             Smith

मैं LINQ के लिए नया हूं (इसलिए मुझे माफ कर दो अगर मैं लंगड़ा हो रहा हूं) और 'LINQ Outer Joins' के लिए काफी कुछ समाधान मिल गया है, जो सभी काफी समान दिखते हैं, लेकिन वास्तव में बाहरी जोड़ लगते हैं।

मेरे प्रयास अब तक कुछ इस तरह हैं:

private void OuterJoinTest()
{
    List<FirstName> firstNames = new List<FirstName>();
    firstNames.Add(new FirstName { ID = 1, Name = "John" });
    firstNames.Add(new FirstName { ID = 2, Name = "Sue" });

    List<LastName> lastNames = new List<LastName>();
    lastNames.Add(new LastName { ID = 1, Name = "Doe" });
    lastNames.Add(new LastName { ID = 3, Name = "Smith" });

    var outerJoin = from first in firstNames
        join last in lastNames
        on first.ID equals last.ID
        into temp
        from last in temp.DefaultIfEmpty()
        select new
        {
            id = first != null ? first.ID : last.ID,
            firstname = first != null ? first.Name : string.Empty,
            surname = last != null ? last.Name : string.Empty
        };
    }
}

public class FirstName
{
    public int ID;

    public string Name;
}

public class LastName
{
    public int ID;

    public string Name;
}

लेकिन यह रिटर्न:

ID  FirstName  LastName
--  ---------  --------
 1  John       Doe
 2  Sue

मैं क्या गलत कर रहा हूं?


2
क्या आपको केवल इन-मेमोरी सूचियों के लिए या Linq2Sql के लिए काम करने की आवश्यकता है?
जेम्सफाइक्स

प्रयास करें .GroupJoin () stackoverflow.com/questions/15595289/…
jdev.ninja

जवाबों:


122

मुझे नहीं पता कि यह सभी मामलों को कवर करता है, तार्किक रूप से यह सही लगता है। विचार यह है कि बाएँ बाहरी जुड़ाव और दाएँ बाहरी जुड़ने के बाद परिणामों का संघ लिया जाए।

var firstNames = new[]
{
    new { ID = 1, Name = "John" },
    new { ID = 2, Name = "Sue" },
};
var lastNames = new[]
{
    new { ID = 1, Name = "Doe" },
    new { ID = 3, Name = "Smith" },
};
var leftOuterJoin =
    from first in firstNames
    join last in lastNames on first.ID equals last.ID into temp
    from last in temp.DefaultIfEmpty()
    select new
    {
        first.ID,
        FirstName = first.Name,
        LastName = last?.Name,
    };
var rightOuterJoin =
    from last in lastNames
    join first in firstNames on last.ID equals first.ID into temp
    from first in temp.DefaultIfEmpty()
    select new
    {
        last.ID,
        FirstName = first?.Name,
        LastName = last.Name,
    };
var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin);

यह तब से काम करता है जैसा कि LINQ में ऑब्जेक्ट्स में लिखा गया है। यदि LINQ SQL या अन्य के लिए, क्वेरी प्रोसेसर सुरक्षित नेविगेशन या अन्य संचालन का समर्थन नहीं कर सकता है। आपको सशर्त ऑपरेटर को मूल्यों को सशर्त रूप से प्राप्त करने के लिए उपयोग करना होगा।

अर्थात,

var leftOuterJoin =
    from first in firstNames
    join last in lastNames on first.ID equals last.ID into temp
    from last in temp.DefaultIfEmpty()
    select new
    {
        first.ID,
        FirstName = first.Name,
        LastName = last != null ? last.Name : default,
    };

2
संघ नकल को खत्म करेगा। यदि आप डुप्लिकेट की उम्मीद नहीं कर रहे हैं, या पहले में शामिल किसी भी चीज़ को बाहर करने के लिए दूसरी क्वेरी लिख सकते हैं, तो इसके बजाय कॉनैट का उपयोग करें। यह UNION और UNION ALL
Cadrell0

3
@ Cadre110 डुप्लिकेट तब होगा जब किसी व्यक्ति का पहला नाम और अंतिम नाम होगा, इसलिए संघ एक वैध विकल्प है।
सास

1
@ कारण लेकिन एक आईडी कॉलम है, इसलिए यहां तक ​​कि अगर कोई डुप्लिकेट पहला और अंतिम नाम है, तो आईडी अलग होनी चाहिए
Cadrell0

1
आपका समाधान आदिम प्रकारों के लिए काम करता है, लेकिन वस्तुओं के लिए काम नहीं करता है। मेरे मामले में, FirstName एक डोमेन ऑब्जेक्ट है, जबकि LastName एक अन्य डोमेन ऑब्जेक्ट है। जब मैं दो परिणामों को मिलाता हूं, तो LINQ ने NotSupportedException को फेंक दिया (यूनियन या कॉनैट में प्रकार असंगत रूप से निर्मित होते हैं)। क्या आपने भी ऐसी ही समस्याओं का अनुभव किया है?
कैंडी चिउ

1
@ कैंडी: मैं वास्तव में इस तरह के मामले में कभी नहीं भागा। मुझे लगता है कि आपके क्वेरी प्रदाता के साथ एक सीमा है। आप संभवतः AsEnumerable()संघ / संघटन करने से पहले कॉल करके उस स्थिति में LINQ वस्तुओं का उपयोग करना चाहेंगे । कोशिश करो और देखो कि कैसे जाता है। यदि यह वह मार्ग नहीं है जिस पर आप जाना चाहते हैं, तो मुझे यकीन नहीं है कि मैं इससे अधिक मदद कर सकता हूं।
जेफ मर्काडो

196

अद्यतन 1: वास्तव में सामान्यीकृत विस्तार विधि प्रदान करना FullOuterJoin
अद्यतन 2: वैकल्पिक रूप IEqualityComparerसे कुंजी प्रकार
3 के लिए एक कस्टम को स्वीकार करना : यह कार्यान्वयन हाल ही मेंMoreLinq - का हिस्सा बन गया है - धन्यवाद दोस्तों!

संपादित करें जोड़ा गया FullOuterGroupJoin( ideone )। मैंने GetOuter<>कार्यान्वयन का पुन: उपयोग किया, जिससे यह एक अंश कम परफ़ॉर्मेंट हो सकता है, लेकिन मैं 'हाईलेवल' कोड के लिए लक्ष्य कर रहा हूं, अभी ब्लीडिंग-एज को अनुकूलित नहीं किया गया है।

इसे लाइव देखें http://ideone.com/O36nWc देखें

static void Main(string[] args)
{
    var ax = new[] { 
        new { id = 1, name = "John" },
        new { id = 2, name = "Sue" } };
    var bx = new[] { 
        new { id = 1, surname = "Doe" },
        new { id = 3, surname = "Smith" } };

    ax.FullOuterJoin(bx, a => a.id, b => b.id, (a, b, id) => new {a, b})
        .ToList().ForEach(Console.WriteLine);
}

प्रिंट का उत्पादन:

{ a = { id = 1, name = John }, b = { id = 1, surname = Doe } }
{ a = { id = 2, name = Sue }, b =  }
{ a = , b = { id = 3, surname = Smith } }

आप चूक की आपूर्ति भी कर सकते हैं: http://ideone.com/kG4kqO

    ax.FullOuterJoin(
            bx, a => a.id, b => b.id, 
            (a, b, id) => new { a.name, b.surname },
            new { id = -1, name    = "(no firstname)" },
            new { id = -2, surname = "(no surname)" }
        )

मुद्रण:

{ name = John, surname = Doe }
{ name = Sue, surname = (no surname) }
{ name = (no firstname), surname = Smith }

प्रयुक्त शब्दों की व्याख्या:

जुड़ना एक शब्द है जो रिलेशनल डेटाबेस डिज़ाइन से उधार लिया गया है:

  • एक में शामिल होने से तत्वों दोहराया जाएगा aके रूप में वहाँ में तत्व हैं कई बार के रूप b इसी कुंजी के साथ (यानी: कुछ भी नहीं करता है, तो bखाली थे)। डेटाबेस लिंगो यह कहता हैinner (equi)join
  • एक बाहरी जुड़ाव में ऐसे तत्व शामिल हैं aजिनके लिए कोई संगत तत्व मौजूद नहीं है b। (यानी: परिणाम भी अगर bखाली थे)। यह आमतौर पर के रूप में जाना जाता हैleft join
  • एक पूर्ण बाहरी में शामिल होने से रिकॉर्ड शामिल हैं a और साथ हीb अगर कोई इसी तत्व अन्य में मौजूद है। (यानी परिणाम भी अगर aखाली थे)

कुछ नहीं आमतौर पर आरडीबीएमएस में देखा एक समूह में शामिल है [1] :

  • एक समूह में शामिल होने , एक ही ऊपर, वर्णित के रूप में करता है , लेकिन बजाय से तत्वों को दोहराने की aइसी कई के लिए b, यह समूह इसी कुंजी के साथ रिकॉर्ड। जब आप एक सामान्य कुंजी के आधार पर 'ज्वाइन' रिकॉर्ड के माध्यम से गणना करना चाहते हैं तो यह अक्सर अधिक सुविधाजनक होता है।

GroupJoin भी देखें जिसमें कुछ सामान्य पृष्ठभूमि स्पष्टीकरण भी हैं।


[१] (मेरा मानना ​​है कि Oracle और MSSQL के पास इसके लिए मालिकाना एक्सटेंशन है)

पूर्ण कोड

इसके लिए एक सामान्यीकृत 'ड्रॉप-इन' एक्सटेंशन क्लास

internal static class MyExtensions
{
    internal static IEnumerable<TResult> FullOuterGroupJoin<TA, TB, TKey, TResult>(
        this IEnumerable<TA> a,
        IEnumerable<TB> b,
        Func<TA, TKey> selectKeyA, 
        Func<TB, TKey> selectKeyB,
        Func<IEnumerable<TA>, IEnumerable<TB>, TKey, TResult> projection,
        IEqualityComparer<TKey> cmp = null)
    {
        cmp = cmp?? EqualityComparer<TKey>.Default;
        var alookup = a.ToLookup(selectKeyA, cmp);
        var blookup = b.ToLookup(selectKeyB, cmp);

        var keys = new HashSet<TKey>(alookup.Select(p => p.Key), cmp);
        keys.UnionWith(blookup.Select(p => p.Key));

        var join = from key in keys
                   let xa = alookup[key]
                   let xb = blookup[key]
                   select projection(xa, xb, key);

        return join;
    }

    internal static IEnumerable<TResult> FullOuterJoin<TA, TB, TKey, TResult>(
        this IEnumerable<TA> a,
        IEnumerable<TB> b,
        Func<TA, TKey> selectKeyA, 
        Func<TB, TKey> selectKeyB,
        Func<TA, TB, TKey, TResult> projection,
        TA defaultA = default(TA), 
        TB defaultB = default(TB),
        IEqualityComparer<TKey> cmp = null)
    {
        cmp = cmp?? EqualityComparer<TKey>.Default;
        var alookup = a.ToLookup(selectKeyA, cmp);
        var blookup = b.ToLookup(selectKeyB, cmp);

        var keys = new HashSet<TKey>(alookup.Select(p => p.Key), cmp);
        keys.UnionWith(blookup.Select(p => p.Key));

        var join = from key in keys
                   from xa in alookup[key].DefaultIfEmpty(defaultA)
                   from xb in blookup[key].DefaultIfEmpty(defaultB)
                   select projection(xa, xb, key);

        return join;
    }
}

FullOuterJoinप्रदान की गई विस्तार विधि के उपयोग को दिखाने के लिए संपादित किया गया
sehe

संपादित: FullOuterGroupJoin विस्तार विधि जोड़ा गया
sehe

4
एक शब्दकोश का उपयोग करने के बजाय, आप एक लुकअप का उपयोग कर सकते हैं , जिसमें आपके सहायक एक्सटेंशन विधियों में व्यक्त कार्यक्षमता शामिल है। उदाहरण के लिए, आप के a.GroupBy(selectKeyA).ToDictionary();रूप में a.ToLookup(selectKeyA)और के adict.OuterGet(key)रूप में लिख सकते हैं alookup[key]। कुंजी का संग्रह प्राप्त करना थोड़ा मुश्किल है, हालांकि alookup.Select(x => x.Keys):।
रिस्की मार्टिन

1
@RiskyMartin धन्यवाद! यह, वास्तव में, पूरी बात को और अधिक सुरुचिपूर्ण बनाता है। मैंने उत्तर और आइडोन-एस को अपडेट किया । (मुझे लगता है कि प्रदर्शन को कम किया जाना चाहिए क्योंकि कम वस्तुओं को त्वरित किया जाता है)।
sehe

1
@ स्पष्ट है कि केवल तभी काम करता है जब आपको पता हो कि कुंजी अद्वितीय हैं। और यह / समूहीकरण / के लिए सामान्य मामला नहीं है। इसके अलावा, हाँ, हर तरह से। यदि आप जानते हैं कि हैश परफेक्ट नहीं खींच रहा है (नोड-आधारित कंटेनरों में सिद्धांत रूप में अधिक लागत है, और हैशिंग स्वतंत्र नहीं है और दक्षता हैश फ़ंक्शन / बाल्टी फैल पर निर्भर करती है), तो यह निश्चित रूप से अधिक एल्गोरिदमिक रूप से कुशल होगा। तो, छोटे लोड के लिए मैं उम्मीद थी यह जल्दी नहीं हो सकता है
sehe

27

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

IEnumerable के लिए मुझे Sehe का उत्तर या समान पसंद नहीं है क्योंकि इसमें अत्यधिक मेमोरी का उपयोग होता है (एक साधारण 10000000 दो सूची परीक्षण ने लिनक्पैड को मेरी 32 जीबी मशीन पर मेमोरी से बाहर कर दिया)।

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

तो यहाँ मेरे एक्सटेंशन हैं जो इन सभी मुद्दों को संभालते हैं, SQL उत्पन्न करते हैं और साथ ही LINQ में SQL को सीधे लागू करते हैं, सर्वर पर क्रियान्वित करते हैं, और Enumerables पर दूसरों की तुलना में तेज़ और कम मेमोरी के साथ होते हैं:

public static class Ext {
    public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftItems,
        IEnumerable<TRight> rightItems,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TResult> resultSelector) {

        return from left in leftItems
               join right in rightItems on leftKeySelector(left) equals rightKeySelector(right) into temp
               from right in temp.DefaultIfEmpty()
               select resultSelector(left, right);
    }

    public static IEnumerable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftItems,
        IEnumerable<TRight> rightItems,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TResult> resultSelector) {

        return from right in rightItems
               join left in leftItems on rightKeySelector(right) equals leftKeySelector(left) into temp
               from left in temp.DefaultIfEmpty()
               select resultSelector(left, right);
    }

    public static IEnumerable<TResult> FullOuterJoinDistinct<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftItems,
        IEnumerable<TRight> rightItems,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TResult> resultSelector) {

        return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Union(leftItems.RightOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
    }

    public static IEnumerable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftItems,
        IEnumerable<TRight> rightItems,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TResult> resultSelector) {

        var hashLK = new HashSet<TKey>(from l in leftItems select leftKeySelector(l));
        return rightItems.Where(r => !hashLK.Contains(rightKeySelector(r))).Select(r => resultSelector(default(TLeft),r));
    }

    public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftItems,
        IEnumerable<TRight> rightItems,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TResult> resultSelector)  where TLeft : class {

        return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
    }

    private static Expression<Func<TP, TC, TResult>> CastSMBody<TP, TC, TResult>(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression<Func<TP, TC, TResult>>)ex;

    public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        var sampleAnonLR = new { left = default(TLeft), rightg = default(IEnumerable<TRight>) };
        var parmP = Expression.Parameter(sampleAnonLR.GetType(), "p");
        var parmC = Expression.Parameter(typeof(TRight), "c");
        var argLeft = Expression.PropertyOrField(parmP, "left");
        var newleftrs = CastSMBody(Expression.Lambda(Expression.Invoke(resultSelector, argLeft, parmC), parmP, parmC), sampleAnonLR, default(TRight), default(TResult));

        return leftItems.AsQueryable().GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
    }

    public static IQueryable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        var sampleAnonLR = new { leftg = default(IEnumerable<TLeft>), right = default(TRight) };
        var parmP = Expression.Parameter(sampleAnonLR.GetType(), "p");
        var parmC = Expression.Parameter(typeof(TLeft), "c");
        var argRight = Expression.PropertyOrField(parmP, "right");
        var newrightrs = CastSMBody(Expression.Lambda(Expression.Invoke(resultSelector, parmC, argRight), parmP, parmC), sampleAnonLR, default(TLeft), default(TResult));

        return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).SelectMany(l => l.leftg.DefaultIfEmpty(), newrightrs);
    }

    public static IQueryable<TResult> FullOuterJoinDistinct<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Union(leftItems.RightOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
    }

    private static Expression<Func<TP, TResult>> CastSBody<TP, TResult>(LambdaExpression ex, TP unusedP, TResult unusedRes) => (Expression<Func<TP, TResult>>)ex;

    public static IQueryable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        var sampleAnonLgR = new { leftg = default(IEnumerable<TLeft>), right = default(TRight) };
        var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
        var argLeft = Expression.Constant(default(TLeft), typeof(TLeft));
        var argRight = Expression.PropertyOrField(parmLgR, "right");
        var newrightrs = CastSBody(Expression.Lambda(Expression.Invoke(resultSelector, argLeft, argRight), parmLgR), sampleAnonLgR, default(TResult));

        return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).Where(lgr => !lgr.leftg.Any()).Select(newrightrs);
    }

    public static IQueryable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
    }
}

एक राइट एंटी-सेमी-ज्वाइन के बीच का अंतर ज्यादातर लाइनक के साथ ऑब्जेक्ट्स या सोर्स में म्यूट होता है, लेकिन अंतिम जवाब में सर्वर (SQL) साइड पर फर्क पड़ता है, एक अनावश्यक को हटा देता है JOIN

एक लैम्ब्डा में Expressionविलय करने के लिए हैंड कोडिंग को Expression<Func<>>लिनकिट के साथ बेहतर बनाया जा सकता है, लेकिन यह अच्छा होगा यदि भाषा / संकलक ने इसके लिए कुछ मदद जोड़ी है। FullOuterJoinDistinctऔर RightOuterJoinकार्यों संपूर्णता के लिए शामिल किए गए हैं, लेकिन मैं नहीं फिर से लागू कियाFullOuterGroupJoin अभी तक।

मैंने इसके लिए एक पूर्ण बाहरी जुड़ाव का दूसरा संस्करण लिखाIEnumerable मामलों के जुड़ाव जहां कुंजी क्रमबद्ध है, जो बाएं बाहरी जोड़ को सही विरोधी अर्ध जोड़ के साथ संयोजित करने की तुलना में लगभग 50% तेज है, कम से कम छोटे संग्रह पर। यह सिर्फ एक बार छांटने के बाद प्रत्येक संग्रह से गुजरता है।

मैंने उस संस्करण के लिए एक और उत्तर भी जोड़ा है जो ईएफ के साथ काम करता हैInvoke जो कस्टम विस्तार के ।


क्या है सौदा TP unusedP, TC unusedC? क्या वे सचमुच अप्रयुक्त हैं?
रुडी

हाँ, वे तो बस में प्रकार कब्जा करने के लिए मौजूद हैं TP, TC, TResultउचित बनाने के लिए Expression<Func<>>। मुझे लगता है मैं उन लोगों के साथ बदल सकते चाहिए _, __, ___बजाय, लेकिन किसी भी स्पष्ट प्रतीत नहीं होता है जब तक सी # के बजाय का उपयोग करने के लिए एक उचित पैरामीटर वाइल्डकार्ड है।
NetMage

1
@MarcL। मैं 'थकाऊ' के ​​बारे में इतना निश्चित नहीं हूं - लेकिन मैं मानता हूं कि यह उत्तर इस संदर्भ में बहुत उपयोगी है। प्रभावशाली सामान (हालाँकि मेरे लिए यह Linq-to-SQL की कमियों की पुष्टि करता है)
sehe

3
मैं हो रही है The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.। क्या इस कोड के साथ कोई प्रतिबंध हैं? मैं
लर्नर

1
मैंने एक नया उत्तर जोड़ा है जो Invokeएक कस्टम ExpressionVisitorइनलाइन के साथ बदलता है Invokeइसलिए इसे EF के साथ काम करना चाहिए। क्या आप इसे आजमा सकते हैं?
नेटमैज

7

यहाँ एक विस्तार विधि है कि:

public static IEnumerable<KeyValuePair<TLeft, TRight>> FullOuterJoin<TLeft, TRight>(this IEnumerable<TLeft> leftItems, Func<TLeft, object> leftIdSelector, IEnumerable<TRight> rightItems, Func<TRight, object> rightIdSelector)
{
    var leftOuterJoin = from left in leftItems
        join right in rightItems on leftIdSelector(left) equals rightIdSelector(right) into temp
        from right in temp.DefaultIfEmpty()
        select new { left, right };

    var rightOuterJoin = from right in rightItems
        join left in leftItems on rightIdSelector(right) equals leftIdSelector(left) into temp
        from left in temp.DefaultIfEmpty()
        select new { left, right };

    var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin);

    return fullOuterJoin.Select(x => new KeyValuePair<TLeft, TRight>(x.left, x.right));
}

3
+1। आर ∪ एस = (आर) एस) R (आर, एस), जिसका अर्थ है एक पूर्ण बाहरी जुड़ना = बाएं बाहरी जुड़ाव सभी सही बाहरी जुड़ाव! मैं इस दृष्टिकोण की सादगी की सराहना करता हूं।
तमसुराजॉय

1
@TamusJRoyce के अलावा Unionडुप्लिकेट को हटाता है, इसलिए यदि मूल डेटा में डुप्लिकेट पंक्तियाँ हैं, तो वे परिणाम में नहीं होंगे।
नेटमैज

महान बिंदु! यदि आपको डुप्लिकेट को निकालने से रोकने की आवश्यकता है, तो एक अद्वितीय आईडी जोड़ें। हाँ। संघ थोड़ा बेकार है जब तक आप संकेत नहीं दे सकते कि एक अद्वितीय आईडी है और संघ सभी (आंतरिक उत्तराधिकार / अनुकूलन के माध्यम से) संघ को स्विच करता है। लेकिन यह काम करेगा।
TamusJRoyce


7

मैं अनुमान लगा रहा हूं कि @ सेह का दृष्टिकोण अधिक मजबूत है, लेकिन जब तक मैं इसे बेहतर नहीं समझ पाता, मैं अपने आप को माइकल माइकल के विस्तार से छलांग लगा लेता हूं। मैंने इसे सिंटैक्स से मेल करने के लिए संशोधित किया और यहां वर्णित Enumerable.Join () विधि के प्रकार को वापस किया । मैंने @ JeffMercado के समाधान के तहत @ Cadrell0 की टिप्पणी के संबंध में "अलग" प्रत्यय को जोड़ा।

public static class MyExtensions {

    public static IEnumerable<TResult> FullJoinDistinct<TLeft, TRight, TKey, TResult> (
        this IEnumerable<TLeft> leftItems, 
        IEnumerable<TRight> rightItems, 
        Func<TLeft, TKey> leftKeySelector, 
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TResult> resultSelector
    ) {

        var leftJoin = 
            from left in leftItems
            join right in rightItems 
              on leftKeySelector(left) equals rightKeySelector(right) into temp
            from right in temp.DefaultIfEmpty()
            select resultSelector(left, right);

        var rightJoin = 
            from right in rightItems
            join left in leftItems 
              on rightKeySelector(right) equals leftKeySelector(left) into temp
            from left in temp.DefaultIfEmpty()
            select resultSelector(left, right);

        return leftJoin.Union(rightJoin);
    }

}

उदाहरण में, आप इसे इस तरह उपयोग करेंगे:

var test = 
    firstNames
    .FullJoinDistinct(
        lastNames,
        f=> f.ID,
        j=> j.ID,
        (f,j)=> new {
            ID = f == null ? j.ID : f.ID, 
            leftName = f == null ? null : f.Name,
            rightName = j == null ? null : j.Name
        }
    );

भविष्य में, जैसा कि मैं और अधिक सीखता हूं, मुझे लगता है कि मैं @ sehe के तर्क की ओर पलायन कर रहा हूं, यह लोकप्रियता है। लेकिन फिर भी मुझे सावधान रहना होगा, क्योंकि मुझे लगता है कि कम से कम एक अधिभार होना जरूरी है जो मौजूदा ".Join ()" विधि के वाक्यविन्यास से मेल खाता है यदि संभव हो तो दो कारणों से:

  1. तरीकों में संगति समय बचाने में मदद करती है, त्रुटियों से बचती है, और अनपेक्षित व्यवहार से बचती है।
  2. यदि भविष्य में कभी आउट-ऑफ-द-बॉक्स ".FullJoin ()" विधि है, तो मुझे लगता है कि यह वर्तमान में मौजूद ".Join ()" पद्धति के सिंटैक्स को रखने की कोशिश करेगा यदि यह हो सकता है। यदि ऐसा होता है, तो यदि आप इसे माइग्रेट करना चाहते हैं, तो आप बस मापदंडों को बदलने या विभिन्न रिटर्न प्रकारों के बारे में चिंता किए बिना अपने कार्यों का नाम बदल सकते हैं।

मैं अभी भी जेनरिक, एक्सटेंशन, फंक स्टेटमेंट और अन्य विशेषताओं के साथ नया हूं, इसलिए प्रतिक्रिया निश्चित रूप से स्वागत योग्य है।

संपादित करें: मुझे यह महसूस करने में देर नहीं लगी कि मेरे कोड में कोई समस्या है। मैं LINQPad में एक .Dump () कर रहा था और रिटर्न प्रकार देख रहा था। यह सिर्फ IEnumerable था, इसलिए मैंने इसे मैच करने की कोशिश की। लेकिन जब मैंने वास्तव में एक .Where () या .Select () अपने एक्सटेंशन पर किया था, तो मुझे एक त्रुटि मिली: "'सिस्टम कलेक्शन्स.IEnumerable' में 'Select' और ... के लिए परिभाषा नहीं है।" इसलिए अंत में मैं .Join () के इनपुट सिंटैक्स का मिलान करने में सक्षम था, लेकिन रिटर्न व्यवहार नहीं।

संपादित करें: फ़ंक्शन के लिए वापसी प्रकार में "TResult" जोड़ा गया। याद किया कि जब Microsoft लेख पढ़ रहा है, और निश्चित रूप से यह समझ में आता है। इस फिक्स के साथ, अब ऐसा लगता है कि रिटर्न व्यवहार मेरे लक्ष्यों के अनुरूप है।


+2 इस जवाब के लिए और साथ ही माइकल सैंडर्स। मैंने गलती से इस पर क्लिक कर दिया और वोट लॉक हो गया। कृपया दो जोड़ दें।
TamusJRoyce

@TamusJRoyce, मैं अभी कोड प्रारूपों को थोड़ा संपादित करने के लिए गया था। मेरा मानना ​​है कि एक संपादन किए जाने के बाद, आपके पास अपना वोट पुनः प्राप्त करने का विकल्प है। अगर आपको पसंद है तो इसे एक शॉट दें।

आपको बहुत - बहुत धन्यवाद!
रोशना ओमर

6

जैसा कि आपने पाया है, Linq में एक "बाहरी जुड़ाव" निर्माण नहीं है। आप जो निकटतम प्राप्त कर सकते हैं वह आपके द्वारा बताई गई क्वेरी का उपयोग करके एक बाहरी बाहरी जुड़ाव है। इसके लिए, आप अंतिम नाम सूची के किसी भी तत्व को जोड़ सकते हैं, जो कि जुड़ने का प्रतिनिधित्व नहीं करता है:

outerJoin = outerJoin.Concat(lastNames.Select(l=>new
                            {
                                id = l.ID,
                                firstname = String.Empty,
                                surname = l.Name
                            }).Where(l=>!outerJoin.Any(o=>o.id == l.id)));

2

मुझे सेहे का उत्तर पसंद है, लेकिन यह आस्थगित निष्पादन का उपयोग नहीं करता है (इनपुट अनुक्रम उत्सुकता से ToLookup के लिए कॉल द्वारा गणना किए जाते हैं)। लिनेक-टू-ऑब्जेक्ट्स के लिए .NET स्रोतों को देखने के बाद , मैं इसके साथ आया:

public static class LinqExtensions
{
    public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> left,
        IEnumerable<TRight> right,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TKey, TResult> resultSelector,
        IEqualityComparer<TKey> comparator = null,
        TLeft defaultLeft = default(TLeft),
        TRight defaultRight = default(TRight))
    {
        if (left == null) throw new ArgumentNullException("left");
        if (right == null) throw new ArgumentNullException("right");
        if (leftKeySelector == null) throw new ArgumentNullException("leftKeySelector");
        if (rightKeySelector == null) throw new ArgumentNullException("rightKeySelector");
        if (resultSelector == null) throw new ArgumentNullException("resultSelector");

        comparator = comparator ?? EqualityComparer<TKey>.Default;
        return FullOuterJoinIterator(left, right, leftKeySelector, rightKeySelector, resultSelector, comparator, defaultLeft, defaultRight);
    }

    internal static IEnumerable<TResult> FullOuterJoinIterator<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> left,
        IEnumerable<TRight> right,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TKey, TResult> resultSelector,
        IEqualityComparer<TKey> comparator,
        TLeft defaultLeft,
        TRight defaultRight)
    {
        var leftLookup = left.ToLookup(leftKeySelector, comparator);
        var rightLookup = right.ToLookup(rightKeySelector, comparator);
        var keys = leftLookup.Select(g => g.Key).Union(rightLookup.Select(g => g.Key), comparator);

        foreach (var key in keys)
            foreach (var leftValue in leftLookup[key].DefaultIfEmpty(defaultLeft))
                foreach (var rightValue in rightLookup[key].DefaultIfEmpty(defaultRight))
                    yield return resultSelector(leftValue, rightValue, key);
    }
}

इस कार्यान्वयन में निम्नलिखित महत्वपूर्ण गुण हैं:

  • आवर्ती निष्पादन, इनपुट अनुक्रम आउटपुट अनुक्रम की गणना करने से पहले गणना नहीं किए जाएंगे।
  • केवल एक बार इनपुट दृश्यों की गणना करता है।
  • इनपुट अनुक्रमों के आदेश को संरक्षित करता है, इस अर्थ में कि यह बाएं अनुक्रम के क्रम में ट्यूपल्स का उत्पादन करेगा और फिर दाएं (बाएं अनुक्रम में मौजूद कुंजियों के लिए नहीं)।

ये गुण महत्वपूर्ण हैं, क्योंकि वे ऐसे हैं जो FullOuterJoin के लिए नए हैं, लेकिन LINQ के साथ अनुभवी उम्मीद करेंगे।


यह इनपुट अनुक्रमों के आदेश को संरक्षित नहीं करता है: लुकअप इस बात की गारंटी नहीं देता है, इसलिए ये फ़ॉरेस्ट बाईं ओर के कुछ क्रम में गणना करेंगे, फिर दाईं ओर के कुछ क्रम बाईं ओर मौजूद नहीं हैं। लेकिन तत्वों का संबंधपरक क्रम संरक्षित नहीं है।
इवान डानिलोव

@IvanDanilov आप सही हैं कि यह वास्तव में अनुबंध में नहीं है। हालाँकि, ToLookup का कार्यान्वयन Enumerable.cs में एक आंतरिक लुकअप क्लास का उपयोग करता है, जो सम्मिलन-क्रम वाली लिंक की गई सूची में समूह रखता है और उनके माध्यम से पुनरावृति करने के लिए इस सूची का उपयोग करता है। इसलिए वर्तमान .NET संस्करण में, ऑर्डर की गारंटी दी गई है, लेकिन चूंकि MS ने दुर्भाग्य से यह दस्तावेज नहीं किया है, इसलिए वे इसे बाद के संस्करणों में बदल सकते हैं।
सोरेन बोइसन

मैंने इसे Win 8.1 पर .NET 4.5.1 पर आज़माया, और यह ऑर्डर को संरक्षित नहीं करता है।
इवान डैनिलोव

1
".. इनपुट अनुक्रम उत्सुकता से ToLookup के लिए कॉल द्वारा enumerated हैं"। लेकिन आपका कार्यान्वयन ठीक वैसा ही होता है .. परिमित राज्य मशीन पर खर्च के कारण उपज अधिक नहीं देता है।
pkuderov

4
लुकअप कॉल तब किया जाता है जब परिणाम का पहला तत्व अनुरोध किया जाता है, न कि जब इट्रेटर बनाया जाता है। यही कारण है कि आस्थगित निष्पादन का मतलब है। आप एक इनपुट सेट की गणना को आगे भी स्थगित कर सकते हैं, बाएं Enumerable को पुनरावृत्त करने के बजाय इसे लुकअप में परिवर्तित करने से, अतिरिक्त लाभ के परिणामस्वरूप, बाएं सेट का क्रम संरक्षित है।
रॉल्फ

2

मैंने इसे एक अलग उत्तर के रूप में जोड़ने का फैसला किया क्योंकि मैं सकारात्मक नहीं हूं यह पर्याप्त परीक्षण है। इस का एक पुनः कार्यान्वयन है FullOuterJoinअनिवार्य रूप से एक सरल, का अनुकूलित संस्करण का उपयोग कर विधि LINQKit Invoke/ Expandके लिए Expressionइतना है कि यह इकाई की रूपरेखा काम करना चाहिए। वहाँ बहुत स्पष्टीकरण नहीं है क्योंकि यह मेरे पिछले उत्तर के समान ही है।

public static class Ext {
    private static Expression<Func<TP, TC, TResult>> CastSMBody<TP, TC, TResult>(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression<Func<TP, TC, TResult>>)ex;

    public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        // (lrg,r) => resultSelector(lrg.left, r)
        var sampleAnonLR = new { left = default(TLeft), rightg = default(IEnumerable<TRight>) };
        var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lrg");
        var parmC = Expression.Parameter(typeof(TRight), "r");
        var argLeft = Expression.PropertyOrField(parmP, "left");
        var newleftrs = CastSMBody(Expression.Lambda(resultSelector.Apply(argLeft, parmC), parmP, parmC), sampleAnonLR, default(TRight), default(TResult));

        return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
    }

    public static IQueryable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) {

        // (lgr,l) => resultSelector(l, lgr.right)
        var sampleAnonLR = new { leftg = default(IEnumerable<TLeft>), right = default(TRight) };
        var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lgr");
        var parmC = Expression.Parameter(typeof(TLeft), "l");
        var argRight = Expression.PropertyOrField(parmP, "right");
        var newrightrs = CastSMBody(Expression.Lambda(resultSelector.Apply(parmC, argRight), parmP, parmC), sampleAnonLR, default(TLeft), default(TResult));

        return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
                         .SelectMany(l => l.leftg.DefaultIfEmpty(), newrightrs);
    }

    private static Expression<Func<TParm, TResult>> CastSBody<TParm, TResult>(LambdaExpression ex, TParm unusedP, TResult unusedRes) => (Expression<Func<TParm, TResult>>)ex;

    public static IQueryable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class {

        // newrightrs = lgr => resultSelector(default(TLeft), lgr.right)
        var sampleAnonLgR = new { leftg = (IEnumerable<TLeft>)null, right = default(TRight) };
        var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
        var argLeft = Expression.Constant(default(TLeft), typeof(TLeft));
        var argRight = Expression.PropertyOrField(parmLgR, "right");
        var newrightrs = CastSBody(Expression.Lambda(resultSelector.Apply(argLeft, argRight), parmLgR), sampleAnonLgR, default(TResult));

        return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).Where(lgr => !lgr.leftg.Any()).Select(newrightrs);
    }

    public static IQueryable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
        this IQueryable<TLeft> leftItems,
        IQueryable<TRight> rightItems,
        Expression<Func<TLeft, TKey>> leftKeySelector,
        Expression<Func<TRight, TKey>> rightKeySelector,
        Expression<Func<TLeft, TRight, TResult>> resultSelector)  where TLeft : class where TRight : class where TResult : class {

        return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
    }

    public static Expression Apply(this LambdaExpression e, params Expression[] args) {
        var b = e.Body;

        foreach (var pa in e.Parameters.Cast<ParameterExpression>().Zip(args, (p, a) => (p, a))) {
            b = b.Replace(pa.p, pa.a);
        }

        return b.PropagateNull();
    }

    public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
    public class ReplaceVisitor : System.Linq.Expressions.ExpressionVisitor {
        public readonly Expression from;
        public readonly Expression to;

        public ReplaceVisitor(Expression _from, Expression _to) {
            from = _from;
            to = _to;
        }

        public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
    }

    public static Expression PropagateNull(this Expression orig) => new NullVisitor().Visit(orig);
    public class NullVisitor : System.Linq.Expressions.ExpressionVisitor {
        public override Expression Visit(Expression node) {
            if (node is MemberExpression nme && nme.Expression is ConstantExpression nce && nce.Value == null)
                return Expression.Constant(null, nce.Type.GetMember(nme.Member.Name).Single().GetMemberType());
            else
                return base.Visit(node);
        }
    }

    public static Type GetMemberType(this MemberInfo member) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.FieldType;
            case PropertyInfo mpi:
                return mpi.PropertyType;
            case EventInfo mei:
                return mei.EventHandlerType;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
        }
    }
}

NetMage, प्रभावशाली कोडिंग! जब मैं इसे एक साधारण उदाहरण से चलाता हूं, और जब [NullVisitor.Visit (..) को [base.Visit (Node)] में लगाया जाता है, तो यह एक [System.ArgumentException: Argument Types से मेल नहीं खाता] फेंकता है। यह सच है, जैसा कि मैं [गाइड] TKey का उपयोग कर रहा हूं और कुछ बिंदु पर शून्य आगंतुक एक [गाइड] प्रकार की अपेक्षा करता है। हो सकता है मुझे कुछ याद आ रहा हो। मेरे पास EF 6.4.4 के लिए एक छोटा उदाहरण है। कृपया मुझे बताएं कि मैं इस कोड को आपके साथ कैसे साझा कर सकता हूं। धन्यवाद!
ट्रंचो

@ ट्रंचो मैं आम तौर पर परीक्षण के लिए लिनक्यूपैड का उपयोग करता हूं, इसलिए ईएफ 6 आसानी से नहीं किया जाता है। base.Visit(node)एक अपवाद नहीं फेंकना चाहिए क्योंकि बस पेड़ को नीचे गिरा देता है। मैं किसी भी कोड साझा करने वाली सेवा तक बहुत पहुंच प्राप्त कर सकता हूं, लेकिन परीक्षण डेटाबेस सेटअप नहीं कर सकता। हालाँकि, मेरे LINQ से SQL टेस्ट के लिए इसे चलाना ठीक काम लगता है।
नेटमैज

@ ट्रंचो क्या यह संभव है कि आप एक Guidकुंजी और एक Guid?विदेशी कुंजी के बीच शामिल हो रहे हैं ?
नेटमैज

मैं LinqPad का उपयोग परीक्षण के लिए भी कर रहा हूं। मेरी क्वेरी ने आर्ग्यूमेंट अपवाद को फेंक दिया, इसलिए मैंने इसे VS .2019 पर [.Net फ्रेमवर्क 4.7.1] और नवीनतम EF 6. पर डिबग करने का निर्णय लिया। वहां मुझे असली समस्या का पता लगाने के लिए मिला। आपके कोड का परीक्षण करने के लिए, मैं एक ही [व्यक्ति] तालिका से 2 अलग डेटा सेट उत्पन्न कर रहा हूं। मैं दोनों सेटों को फ़िल्टर करता हूं ताकि कुछ रिकॉर्ड प्रत्येक सेट के लिए अद्वितीय हों और कुछ दोनों सेटों पर मौजूद हों। [PersonId] एक [प्राथमिक कुंजी] गाइड (c #) / Uniqueidentifier (SqlServer) है और न ही कोई शून्य [PersonId] मान उत्पन्न करता है। साझा कोड: github.com/Troncho/EF_FullOuterJoin
Troncho

1

दोनों इनपुट्स पर इन-मेमोरी स्ट्रीमिंग एन्यूमरेशन करता है और प्रत्येक पंक्ति के लिए चयनकर्ता को आमंत्रित करता है। यदि वर्तमान पुनरावृत्ति में कोई संबंध नहीं है, तो चयनकर्ता के तर्क में से एक अशक्त होगा

उदाहरण:

   var result = left.FullOuterJoin(
         right, 
         x=>left.Key, 
         x=>right.Key, 
         (l,r) => new { LeftKey = l?.Key, RightKey=r?.Key });
  • सहसंबंध प्रकार के लिए एक IComparer की आवश्यकता होती है, यदि उपलब्ध नहीं है तो Comparer.Default का उपयोग करता है।

  • इसके लिए आवश्यक है कि 'ऑर्डरबाय' इनपुट एन्यूमरैबल्स पर लागू हो

    /// <summary>
    /// Performs a full outer join on two <see cref="IEnumerable{T}" />.
    /// </summary>
    /// <typeparam name="TLeft"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <typeparam name="TRight"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <param name="leftKeySelector"></param>
    /// <param name="rightKeySelector"></param>
    /// <param name="selector">Expression defining result type</param>
    /// <param name="keyComparer">A comparer if there is no default for the type</param>
    /// <returns></returns>
    [System.Diagnostics.DebuggerStepThrough]
    public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TValue, TResult>(
        this IEnumerable<TLeft> left,
        IEnumerable<TRight> right,
        Func<TLeft, TValue> leftKeySelector,
        Func<TRight, TValue> rightKeySelector,
        Func<TLeft, TRight, TResult> selector,
        IComparer<TValue> keyComparer = null)
        where TLeft: class
        where TRight: class
        where TValue : IComparable
    {
    
        keyComparer = keyComparer ?? Comparer<TValue>.Default;
    
        using (var enumLeft = left.OrderBy(leftKeySelector).GetEnumerator())
        using (var enumRight = right.OrderBy(rightKeySelector).GetEnumerator())
        {
    
            var hasLeft = enumLeft.MoveNext();
            var hasRight = enumRight.MoveNext();
            while (hasLeft || hasRight)
            {
    
                var currentLeft = enumLeft.Current;
                var valueLeft = hasLeft ? leftKeySelector(currentLeft) : default(TValue);
    
                var currentRight = enumRight.Current;
                var valueRight = hasRight ? rightKeySelector(currentRight) : default(TValue);
    
                int compare =
                    !hasLeft ? 1
                    : !hasRight ? -1
                    : keyComparer.Compare(valueLeft, valueRight);
    
                switch (compare)
                {
                    case 0:
                        // The selector matches. An inner join is achieved
                        yield return selector(currentLeft, currentRight);
                        hasLeft = enumLeft.MoveNext();
                        hasRight = enumRight.MoveNext();
                        break;
                    case -1:
                        yield return selector(currentLeft, default(TRight));
                        hasLeft = enumLeft.MoveNext();
                        break;
                    case 1:
                        yield return selector(default(TLeft), currentRight);
                        hasRight = enumRight.MoveNext();
                        break;
                }
            }
    
        }
    
    }

1
वह चीजों को "स्ट्रीमिंग" बनाने का एक वीर प्रयास है। अफसोस की बात है कि पहले चरण में सभी लाभ खो जाते हैं, जहां आप OrderByदोनों महत्वपूर्ण अनुमानों पर प्रदर्शन करते हैं । OrderByस्पष्ट कारणों के लिए पूरे क्रम को बफ़र करता है
21

@sehe आप निश्चित रूप से वस्तुओं के लिए Linq के लिए सही हैं। यदि IEnumerable <T> IQueryable हैं <T> स्रोत को सॉर्ट करना चाहिए - परीक्षण करने का कोई समय नहीं। अगर मैं इसके बारे में गलत हूं, तो बस IEnumerable <T> इनपुट की जगह IQueryable <T> को स्रोत / डेटाबेस में सॉर्ट करना चाहिए।
जेम्स कारडॉक-डेविस

1

स्थिति के लिए मेरा साफ समाधान दोनों कुंजी में अद्वितीय है:

 private static IEnumerable<TResult> FullOuterJoin<Ta, Tb, TKey, TResult>(
            IEnumerable<Ta> a, IEnumerable<Tb> b,
            Func<Ta, TKey> key_a, Func<Tb, TKey> key_b,
            Func<Ta, Tb, TResult> selector)
        {
            var alookup = a.ToLookup(key_a);
            var blookup = b.ToLookup(key_b);
            var keys = new HashSet<TKey>(alookup.Select(p => p.Key));
            keys.UnionWith(blookup.Select(p => p.Key));
            return keys.Select(key => selector(alookup[key].FirstOrDefault(), blookup[key].FirstOrDefault()));
        }

इसलिए

    var ax = new[] {
        new { id = 1, first_name = "ali" },
        new { id = 2, first_name = "mohammad" } };
    var bx = new[] {
        new { id = 1, last_name = "rezaei" },
        new { id = 3, last_name = "kazemi" } };

    var list = FullOuterJoin(ax, bx, a => a.id, b => b.id, (a, b) => "f: " + a?.first_name + " l: " + b?.last_name).ToArray();

आउटपुट:

f: ali l: rezaei
f: mohammad l:
f:  l: kazemi

0

दो या अधिक तालिकाओं के लिए पूर्ण बाहरी जुड़ना: पहले उस स्तंभ को निकालें जिसे आप जुड़ना चाहते हैं।

var DatesA = from A in db.T1 select A.Date; 
var DatesB = from B in db.T2 select B.Date; 
var DatesC = from C in db.T3 select C.Date;            

var Dates = DatesA.Union(DatesB).Union(DatesC); 

फिर निकाले गए कॉलम और मुख्य तालिकाओं के बीच बाएं बाहरी जोड़ का उपयोग करें।

var Full_Outer_Join =

(from A in Dates
join B in db.T1
on A equals B.Date into AB 

from ab in AB.DefaultIfEmpty()
join C in db.T2
on A equals C.Date into ABC 

from abc in ABC.DefaultIfEmpty()
join D in db.T3
on A equals D.Date into ABCD

from abcd in ABCD.DefaultIfEmpty() 
select new { A, ab, abc, abcd })
.AsEnumerable();

0

मैंने शायद 6 साल पहले एक ऐप के लिए इस एक्सटेंशन क्लास को लिखा है, और बिना मुद्दों के कई समाधानों में इसका इस्तेमाल कर रहा हूं। आशा करता हूँ की ये काम करेगा।

संपादित करें: मैंने देखा कि कुछ को विस्तार वर्ग का उपयोग करने का तरीका नहीं पता होगा।

इस एक्सटेंशन क्लास का उपयोग करने के लिए, जोंनेक्स्ट का उपयोग करके निम्नलिखित लाइन को जोड़कर अपनी कक्षा में इसके नाम स्थान का संदर्भ लें;

^ यह आपको किसी भी IEnumerable ऑब्जेक्ट संग्रह का उपयोग करने के लिए होने वाले विस्तार कार्यों की गहनता को देखने की अनुमति देनी चाहिए।

उम्मीद है की यह मदद करेगा। मुझे बताएं कि क्या यह अभी भी स्पष्ट नहीं है, और मैं उम्मीद करता हूं कि इसका उपयोग करने के तरीके पर एक नमूना उदाहरण लिखूंगा।

अब यहाँ वर्ग है:

namespace joinext
{    
public static class JoinExtensions
    {
        public static IEnumerable<TResult> FullOuterJoin<TOuter, TInner, TKey, TResult>(
            this IEnumerable<TOuter> outer,
            IEnumerable<TInner> inner,
            Func<TOuter, TKey> outerKeySelector,
            Func<TInner, TKey> innerKeySelector,
            Func<TOuter, TInner, TResult> resultSelector)
            where TInner : class
            where TOuter : class
        {
            var innerLookup = inner.ToLookup(innerKeySelector);
            var outerLookup = outer.ToLookup(outerKeySelector);

            var innerJoinItems = inner
                .Where(innerItem => !outerLookup.Contains(innerKeySelector(innerItem)))
                .Select(innerItem => resultSelector(null, innerItem));

            return outer
                .SelectMany(outerItem =>
                {
                    var innerItems = innerLookup[outerKeySelector(outerItem)];

                    return innerItems.Any() ? innerItems : new TInner[] { null };
                }, resultSelector)
                .Concat(innerJoinItems);
        }


        public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
            this IEnumerable<TOuter> outer,
            IEnumerable<TInner> inner,
            Func<TOuter, TKey> outerKeySelector,
            Func<TInner, TKey> innerKeySelector,
            Func<TOuter, TInner, TResult> resultSelector)
        {
            return outer.GroupJoin(
                inner,
                outerKeySelector,
                innerKeySelector,
                (o, i) =>
                    new { o = o, i = i.DefaultIfEmpty() })
                    .SelectMany(m => m.i.Select(inn =>
                        resultSelector(m.o, inn)
                        ));

        }



        public static IEnumerable<TResult> RightJoin<TOuter, TInner, TKey, TResult>(
            this IEnumerable<TOuter> outer,
            IEnumerable<TInner> inner,
            Func<TOuter, TKey> outerKeySelector,
            Func<TInner, TKey> innerKeySelector,
            Func<TOuter, TInner, TResult> resultSelector)
        {
            return inner.GroupJoin(
                outer,
                innerKeySelector,
                outerKeySelector,
                (i, o) =>
                    new { i = i, o = o.DefaultIfEmpty() })
                    .SelectMany(m => m.o.Select(outt =>
                        resultSelector(outt, m.i)
                        ));

        }

    }
}

1
दुर्भाग्य से, ऐसा लगता है कि फ़ंक्शन को SelectManyLINQ2SQL- योग्य अभिव्यक्ति ट्री में परिवर्तित नहीं किया जा सकता है, ऐसा लगता है।
या मैपर

edc65। मुझे पता है कि यह एक मूर्खतापूर्ण सवाल हो सकता है यदि आपने ऐसा पहले ही कर लिया है। लेकिन सिर्फ मामले में (जैसा कि मैंने देखा है कि कुछ नहीं जानते हैं), आपको सिर्फ नेमस्पेस जोंटेक्स्ट का संदर्भ देना होगा।
H7O

या मैपर, मुझे पता है कि आप किस प्रकार के संग्रह के साथ काम करना चाहते थे। यह किसी भी IEnumerable संग्रह के साथ ठीक काम करना चाहिए
H7O

0

मुझे लगता है कि LINQ ज्वाइन क्लॉज इस समस्या का सही समाधान नहीं है, क्योंकि ज्वाइन क्लॉज उद्देश्य इस कार्य समाधान के लिए आवश्यक तरीके से डेटा को संचित नहीं करना है। बनाए गए अलग-अलग संग्रह को मर्ज करने का कोड बहुत जटिल हो जाता है, शायद यह सीखने के उद्देश्यों के लिए ठीक है, लेकिन वास्तविक अनुप्रयोगों के लिए नहीं। इस समस्या को हल करने के तरीकों में से एक नीचे दिए गए कोड में है:

class Program
{
    static void Main(string[] args)
    {
        List<FirstName> firstNames = new List<FirstName>();
        firstNames.Add(new FirstName { ID = 1, Name = "John" });
        firstNames.Add(new FirstName { ID = 2, Name = "Sue" });

        List<LastName> lastNames = new List<LastName>();
        lastNames.Add(new LastName { ID = 1, Name = "Doe" });
        lastNames.Add(new LastName { ID = 3, Name = "Smith" });

        HashSet<int> ids = new HashSet<int>();
        foreach (var name in firstNames)
        {
            ids.Add(name.ID);
        }
        foreach (var name in lastNames)
        {
            ids.Add(name.ID);
        }
        List<FullName> fullNames = new List<FullName>();
        foreach (int id in ids)
        {
            FullName fullName = new FullName();
            fullName.ID = id;
            FirstName firstName = firstNames.Find(f => f.ID == id);
            fullName.FirstName = firstName != null ? firstName.Name : string.Empty;
            LastName lastName = lastNames.Find(l => l.ID == id);
            fullName.LastName = lastName != null ? lastName.Name : string.Empty;
            fullNames.Add(fullName);
        }
    }
}
public class FirstName
{
    public int ID;

    public string Name;
}

public class LastName
{
    public int ID;

    public string Name;
}
class FullName
{
    public int ID;

    public string FirstName;

    public string LastName;
}

यदि असली संग्रह HashSet गठन के लिए बड़े होते हैं, तो नीचे दिए गए कोड का उपयोग किया जा सकता है:

List<int> firstIds = firstNames.Select(f => f.ID).ToList();
List<int> LastIds = lastNames.Select(l => l.ID).ToList();
HashSet<int> ids = new HashSet<int>(firstIds.Union(LastIds));//Only unique IDs will be included in HashSet

0

रोचक पोस्ट के लिए आप सभी का धन्यवाद!

मैंने कोड को संशोधित किया क्योंकि मेरे मामले में मुझे ज़रूरत थी

  • एक व्यक्तिगत जुड़ाव विधेय के लिए
  • एक व्यक्तिगत संघ अलग तुलना

रुचि रखने वालों के लिए यह मेरा संशोधित कोड है (VB, क्षमा करें)

    Module MyExtensions
        <Extension()>
        Friend Function FullOuterJoin(Of TA, TB, TResult)(ByVal a As IEnumerable(Of TA), ByVal b As IEnumerable(Of TB), ByVal joinPredicate As Func(Of TA, TB, Boolean), ByVal projection As Func(Of TA, TB, TResult), ByVal comparer As IEqualityComparer(Of TResult)) As IEnumerable(Of TResult)
            Dim joinL =
                From xa In a
                From xb In b.Where(Function(x) joinPredicate(xa, x)).DefaultIfEmpty()
                Select projection(xa, xb)
            Dim joinR =
                From xb In b
                From xa In a.Where(Function(x) joinPredicate(x, xb)).DefaultIfEmpty()
                Select projection(xa, xb)
            Return joinL.Union(joinR, comparer)
        End Function
    End Module

    Dim fullOuterJoin = lefts.FullOuterJoin(
        rights,
        Function(left, right) left.Code = right.Code And (left.Amount [...] Or left.Description.Contains [...]),
        Function(left, right) New CompareResult(left, right),
        New MyEqualityComparer
    )

    Public Class MyEqualityComparer
        Implements IEqualityComparer(Of CompareResult)

        Private Function GetMsg(obj As CompareResult) As String
            Dim msg As String = ""
            msg &= obj.Code & "_"
            [...]
            Return msg
        End Function

        Public Overloads Function Equals(x As CompareResult, y As CompareResult) As Boolean Implements IEqualityComparer(Of CompareResult).Equals
            Return Me.GetMsg(x) = Me.GetMsg(y)
        End Function

        Public Overloads Function GetHashCode(obj As CompareResult) As Integer Implements IEqualityComparer(Of CompareResult).GetHashCode
            Return Me.GetMsg(obj).GetHashCode
        End Function
    End Class

0

फिर भी एक और पूर्ण बाहरी जुड़ना

जैसा कि सादगी और अन्य प्रस्तावों की पठनीयता से खुश नहीं था, मैं इसके साथ समाप्त हुआ:

यह तेज होने का दिखावा नहीं है (लगभग 800 एमएस एक 2020m सीपीयू पर 2.4 * 1000/1000 में शामिल होने के लिए: 2.4ghz / 2kores)। मेरे लिए, यह सिर्फ एक कॉम्पैक्ट और आकस्मिक पूर्ण बाहरी जुड़ाव है।

यह SQL FULL OUTER JOIN (डुप्लिकेट संरक्षण) के समान है

चीयर्स ;-)

using System;
using System.Collections.Generic;
using System.Linq;
namespace NS
{
public static class DataReunion
{
    public static List<Tuple<T1, T2>> FullJoin<T1, T2, TKey>(List<T1> List1, Func<T1, TKey> KeyFunc1, List<T2> List2, Func<T2, TKey> KeyFunc2)
    {
        List<Tuple<T1, T2>> result = new List<Tuple<T1, T2>>();

        Tuple<TKey, T1>[] identifiedList1 = List1.Select(_ => Tuple.Create(KeyFunc1(_), _)).OrderBy(_ => _.Item1).ToArray();
        Tuple<TKey, T2>[] identifiedList2 = List2.Select(_ => Tuple.Create(KeyFunc2(_), _)).OrderBy(_ => _.Item1).ToArray();

        identifiedList1.Where(_ => !identifiedList2.Select(__ => __.Item1).Contains(_.Item1)).ToList().ForEach(_ => {
            result.Add(Tuple.Create<T1, T2>(_.Item2, default(T2)));
        });

        result.AddRange(
            identifiedList1.Join(identifiedList2, left => left.Item1, right => right.Item1, (left, right) => Tuple.Create<T1, T2>(left.Item2, right.Item2)).ToList()
        );

        identifiedList2.Where(_ => !identifiedList1.Select(__ => __.Item1).Contains(_.Item1)).ToList().ForEach(_ => {
            result.Add(Tuple.Create<T1, T2>(default(T1), _.Item2));
        });

        return result;
    }
}
}

विचार करना है

  1. दिए गए कुंजी फ़ंक्शन बिल्डरों के आधार पर Ids बनाएँ
  2. प्रक्रिया केवल आइटम को छोड़ दिया
  3. प्रोसेस इनर जॉइन करें
  4. सही आइटम केवल प्रक्रिया

यहाँ एक सफल परीक्षा है जो इसके साथ जाती है:

मैन्युअल रूप से यह सत्यापित करने के लिए कि यह अपेक्षित है, अंत में एक विराम बिंदु रखें

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NS;

namespace Tests
{
[TestClass]
public class DataReunionTest
{
    [TestMethod]
    public void Test()
    {
        List<Tuple<Int32, Int32, String>> A = new List<Tuple<Int32, Int32, String>>();
        List<Tuple<Int32, Int32, String>> B = new List<Tuple<Int32, Int32, String>>();

        Random rnd = new Random();

        /* Comment the testing block you do not want to run
        /* Solution to test a wide range of keys*/

        for (int i = 0; i < 500; i += 1)
        {
            A.Add(Tuple.Create(rnd.Next(1, 101), rnd.Next(1, 101), "A"));
            B.Add(Tuple.Create(rnd.Next(1, 101), rnd.Next(1, 101), "B"));
        }

        /* Solution for essential testing*/

        A.Add(Tuple.Create(1, 2, "B11"));
        A.Add(Tuple.Create(1, 2, "B12"));
        A.Add(Tuple.Create(1, 3, "C11"));
        A.Add(Tuple.Create(1, 3, "C12"));
        A.Add(Tuple.Create(1, 3, "C13"));
        A.Add(Tuple.Create(1, 4, "D1"));

        B.Add(Tuple.Create(1, 1, "A21"));
        B.Add(Tuple.Create(1, 1, "A22"));
        B.Add(Tuple.Create(1, 1, "A23"));
        B.Add(Tuple.Create(1, 2, "B21"));
        B.Add(Tuple.Create(1, 2, "B22"));
        B.Add(Tuple.Create(1, 2, "B23"));
        B.Add(Tuple.Create(1, 3, "C2"));
        B.Add(Tuple.Create(1, 5, "E2"));

        Func<Tuple<Int32, Int32, String>, Tuple<Int32, Int32>> key = (_) => Tuple.Create(_.Item1, _.Item2);

        var watch = System.Diagnostics.Stopwatch.StartNew();
        var res = DataReunion.FullJoin(A, key, B, key);
        watch.Stop();
        var elapsedMs = watch.ElapsedMilliseconds;
        String aser = JToken.FromObject(res).ToString(Formatting.Indented);
        Console.Write(elapsedMs);
    }
}

}


-4

मैं वास्तव में इन linq अभिव्यक्तियों से नफरत करता हूं, यही कारण है कि SQL मौजूद है:

select isnull(fn.id, ln.id) as id, fn.firstname, ln.lastname
   from firstnames fn
   full join lastnames ln on ln.id=fn.id

इसे डेटाबेस में sql दृश्य के रूप में बनाएँ और इसे इकाई के रूप में आयात करें।

बेशक, बाएं और दाएं जोड़ों का (विशिष्ट) मिलन इसे भी बना देगा, लेकिन यह बेवकूफी है।


11
क्यों नहीं संभव के रूप में बस के रूप में कई सार ड्रॉप और मशीन कोड में ऐसा करते हैं? (संकेत: क्योंकि उच्च क्रम के सार प्रोग्रामर के लिए जीवन को आसान बनाते हैं)। यह सवाल का जवाब नहीं देता है और मुझे LINQ के खिलाफ एक शेख़ी की तरह लगता है।
खर्चा

8
किसने कहा कि डेटा एक डेटाबेस से आता है?
14:24 पर user247702

1
बेशक, यह डेटाबेस है, सवाल में "बाहरी
जुड़ाव

1
मैं समझता हूं कि यह "पुराना फैशन" समाधान है, लेकिन डाउनवोटिंग से पहले, इसकी जटिलता की अन्य समाधानों के साथ तुलना करें :) स्वीकृत एक को छोड़कर, यह निश्चित रूप से सही है।
मिलान .vec

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