如何在Linq中进行完全外连接?

Sha*_*ica 36 c# linq outer-join linq-to-sql full-outer-join

我继承了一个没有完全优化设计的数据库,我需要操作一些数据.让我对我必须做的事情做一个更常见的比喻:

假设我们有一张Student桌子,一张StudentClass桌子记录了他参加的所有课程,还有一张StudentTeacher桌子可以存储教授这名学生的所有老师.是的,我知道这是一个愚蠢的设计,将老师存放在Class表上更有意义 - 但这就是我们正在使用的.

我现在想要清理数据,我想找到一个学生有老师但没有上课但是上课但没有老师的所有地方.SQL因此:

select *
from StudentClass sc
full outer join StudentTeacher st on st.StudentID = sc.StudentID
where st.id is null or sc.id is null
Run Code Online (Sandbox Code Playgroud)

你是怎么在Linq那样做的?

Sha*_*ica 28

我想我的答案在这里,并不像我希望的那样优雅,但它应该做到这一点:

var studentIDs = StudentClasses.Select(sc => sc.StudentID)
  .Union(StudentTeachers.Select(st => st.StudentID);
  //.Distinct(); -- Distinct not necessary after Union
var q =
  from id in studentIDs
  join sc in StudentClasses on id equals sc.StudentID into jsc
  from sc in jsc.DefaultIfEmpty()
  join st in StudentTeachers on id equals st.StudentID into jst
  from st in jst.DefaultIfEmpty()
  where st == null ^ sc == null
  select new { sc, st };
Run Code Online (Sandbox Code Playgroud)

您可以将这两个语句压缩成一个,但我认为您会牺牲代码清晰度.

  • Set Unions自动制作独特的东西http://en.wikipedia.org/wiki/Union_(set_theory) (3认同)

Bor*_*itz 18

for the given 2 collections a and b, a required full outer join might be as following:

a.Union(b).Except(a.Intersect(b));
Run Code Online (Sandbox Code Playgroud)

If a and b are not of the same type, then 2 separate left outer joins are required:

var studentsWithoutTeachers =
    from sc in studentClasses
    join st in studentTeachers on sc.StudentId equals st.StudentId into g
    from st in g.DefaultIfEmpty()
    where st == null
    select sc;
var teachersWithoutStudents =
    from st in studentTeachers
    join sc in studentClasses on st.StudentId equals sc.StudentId into g
    from sc in g.DefaultIfEmpty()
    where sc == null
    select st;
Run Code Online (Sandbox Code Playgroud)

here is a one line option using Concat():

(from l in left
 join r in right on l.Id equals r.Id into g
 from r in g.DefaultIfEmpty()
 where r == null
 select new {l, r})
     .Concat(
     from r in right
     join sc in left on r.Id equals sc.Id into g
     from l in g.DefaultIfEmpty()
     where l == null
     select new {l, r});
Run Code Online (Sandbox Code Playgroud)

  • @grzegorz_p msdn 示例显示了 LEFT 外连接。问题是关于 FULL 外连接 (2认同)

and*_*nov 18

扩展方法:

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);
            }
Run Code Online (Sandbox Code Playgroud)

测试:

[Test]
public void CanDoFullOuterJoin()
{
    var list1 = new[] {"A", "B"};
    var list2 = new[] { "B", "C" };

    list1.FullOuterJoin(list2, x => x, x => x, (x1, x2) => (x1 ?? "") + (x2 ?? ""))
         .ShouldCollectionEqual(new [] { "A", "BB", "C"} );
}
Run Code Online (Sandbox Code Playgroud)