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

A는 (외부 왼쪽) 테이블 조인 Bar테이블로 FooFoo.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을 수행하므로 선택하려는 항목에 따라 파트 만 필요합니다.
George Mauer

6
이 패턴은 Entity Framework
가이

3
@nam 글쎄, 당신은 where 문이 필요합니다. x.Bar == null
Tod

2
@AbdulkarimKanaan yes-SelectMany는 1-many의 두 레이어를 한 쌍으로 입력하여 한 레이어로 병합합니다
Marc Gravell

1
@MarcGravell 나는 코드 조각에서 당신이 한 일에 대한 약간의 설명을 추가하기 위해 편집을 제안했습니다.
B--rian

109

이것은 메서드 (확장) 구문을 사용하는 왼쪽 외부 조인에 대한 사실상의 SO 질문 인 것처럼 보이기 때문에 (최소한의 경험으로는) 내가 선택한 것보다 현재 선택된 답변에 대한 대안을 추가 할 것이라고 생각했습니다. 후

// 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
다음과 같은 다른 Join 또는 Include가 있으면 "bs.SingleOrDefault ()"가 작동하지 않는다고 생각합니다. 이 경우 "bs.FirstOrDefault ()"가 필요합니다.
Dherik

3
사실 Entity Framework와 Linq to SQL은 모두 Single조인 중에 쉽게 확인할 수 없기 때문에 필요합니다 . SingleOrDefault그러나이 IMO를 설명하는보다 "올바른"방법입니다.
Ocelot20

1
조인 된 테이블을 주문해야합니다. 그렇지 않으면 .FirstOrDefault ()는 데이터베이스가 처음으로 발견되는 모든 조인 기준과 일치 할 수있는 여러 행에서 임의의 행을 가져옵니다.
Chris Moschini

1
@ChrisMoschini : Order와 FirstOrDefault는 불필요합니다. 예는 여러 레코드에서 실패하려는 0 또는 1 개의 일치에 대한 것입니다 (위의 주석 참조).
Ocelot20

2
이것은 질문에 명시되지 않은 "추가 요구 사항"이 아니며, "왼쪽 외부 조인"이라고 말할 때 많은 사람들이 생각하는 것입니다. 또한 Dherik이 참조하는 FirstOrDefault 요구 사항은 L2Object가 아닌 EF / L2SQL 동작입니다 (둘 다 태그에 없음). 이 경우 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 to SQL은 SQL로 올바르게 변환됩니다. LINQ to Objects의 경우 내부적으로 Lookup을 사용하므로 GroupJoin을 사용하여 가입하는 것이 좋습니다. 그러나 DB를 쿼리하는 경우 GroupJoin을 건너 뛰는 것은 AFAIK입니다.

이 방식의 Personlay는 GroupJoin ()에 비해 읽기 쉽습니다.


이것은 나를 위해 .Join보다 더 잘 수행되었으며, 내가 원하는 조건부 관절을 수행 할 수있었습니다 (right.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 Extensions를 처음 사용하고 있으며이 왼쪽 조인 상황을 둘러 보는 데 어려움을 겪고 있습니다.
Shiva

@ Skychan 그것을보아야 할 수도 있습니다. 오래된 대답이며 그 당시 일하고있었습니다. 어떤 프레임 워크를 사용하고 있습니까? .NET 버전을 의미합니까?
hajirazin

2
이 객체에 Linq에 대한 작동하지만 대신 Funcs의 IQuerable 사용 표현식에서 작동하도록 필요에 따라 데이터베이스를 쿼리하지 않을 때
밥 베일

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

그렇지 않으면 조인에서 어떤 행을 얻을 수 있습니까?


그게 다야! 보증되지 않은 일대일 관계.
it3xl

2

Marc Gravell의 답변을 확장 방법으로 바꾸면서 다음과 같이했습니다.

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 to Objects에 좋지만 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

이것에 대한 쉬운 해결책이 있습니다

Select에서 .HasValue를 사용하십시오.

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

매우 쉽고 그룹 조인이나 다른 것 필요 없음

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.