How do you perform a left outer join using linq extension methods

前端 未结 7 1149
灰色年华
灰色年华 2020-11-22 10:12

Assuming I have a left outer join as such:

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          


        
相关标签:
7条回答
  • 2020-11-22 10:41

    Improving on Ocelot20's answer, if you have a table you're left outer joining with where you just want 0 or 1 rows out of it, but it could have multiple, you need to Order your joined table:

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

    Otherwise which row you get in the join is going to be random (or more specifically, whichever the db happens to find first).

    0 讨论(0)
  • 2020-11-22 10:43

    For a (left outer) join of a table Bar with a table Foo on Foo.Foo_Id = Bar.Foo_Id in lambda notation:

    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});
    
    0 讨论(0)
  • 2020-11-22 10:45

    Since this seems to be the de facto SO question for left outer joins using the method (extension) syntax, I thought I would add an alternative to the currently selected answer that (in my experience at least) has been more commonly what I'm after

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

    To display the difference using a simple data set (assuming we're joining on the values themselves):

    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    }    
    

    Option 2 is true to the typical left outer join definition, but as I mentioned earlier is often unnecessarily complex depending on the data set.

    0 讨论(0)
  • 2020-11-22 10:48

    Group Join method is unnecessary to achieve joining of two data sets.

    Inner Join:

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

    For Left Join just add DefaultIfEmpty()

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

    EF and LINQ to SQL correctly transform to SQL. For LINQ to Objects it is beter to join using GroupJoin as it internally uses Lookup. But if you are querying DB then skipping of GroupJoin is AFAIK as performant.

    Personlay for me this way is more readable compared to GroupJoin().SelectMany()

    0 讨论(0)
  • 2020-11-22 10:56

    Whilst the accepted answer works and is good for Linq to Objects it bugged me that the SQL query isn't just a straight Left Outer Join.

    The following code relies on the LinkKit Project that allows you to pass expressions and invoke them to your query.

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

    It can be used as follows

    Table1.LeftOuterJoin(Table2, x => x.Key1, x => x.Key2, (x,y) => new { x,y});
    
    0 讨论(0)
  • 2020-11-22 10:57

    You can create extension method like:

    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);
        }
    
    0 讨论(0)
提交回复
热议问题