답변:
A는 (외부 왼쪽) 테이블 조인 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});
이것은 메서드 (확장) 구문을 사용하는 왼쪽 외부 조인에 대한 사실상의 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는 일반적인 왼쪽 외부 조인 정의에 해당하지만 앞에서 언급했듯이 데이터 세트에 따라 불필요하게 복잡한 경우가 많습니다.
Single
조인 중에 쉽게 확인할 수 없기 때문에 필요합니다 . SingleOrDefault
그러나이 IMO를 설명하는보다 "올바른"방법입니다.
두 개의 데이터 세트를 결합하기 위해 그룹 결합 방법이 필요하지 않습니다.
내부 조인 :
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 ()에 비해 읽기 쉽습니다.
다음과 같은 확장 방법을 만들 수 있습니다.
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);
}
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() });
그렇지 않으면 조인에서 어떤 행을 얻을 수 있습니까?
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));
}
허용 된 답변이 효과가 있고 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});
GroupJoin
왼쪽 외부 조인SelectMany
을 수행하므로 선택하려는 항목에 따라 파트 만 필요합니다.