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

时间:2010-01-18 10:52:39

标签: c# linq linq-to-sql outer-join 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

你如何在Linq做到这一点?

5 个答案:

答案 0 :(得分: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 };

你可能会把这两个陈述压缩成一个,但我认为你会牺牲代码的清晰度。

答案 1 :(得分:18)

对于给定的2个集合 a b ,所需的完整外部联接可能如下所示:

a.Union(b).Except(a.Intersect(b));

如果a和b的类型不同,则需要2个单独的左外连接

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;

这是使用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});

答案 2 :(得分: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);
            }

测试:

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

答案 3 :(得分:1)

一个开始...

 var q = from sc in StudentClass
            join st in StudentTeachers on sc.StudentID equals st.StudentID into g
            from st in g.DefaultIfEmpty()
            select new {StudentID = sc.StudentID, StudentIDParent = st == null ? "(no StudentTeacher)" : st.StudentID...........};

有关更多样本,另请参阅http://www.linqpad.net/

的好工具

答案 4 :(得分:1)

根据Shaul的回答,但稍加精简:

var q =
  from id in studentIDs
  join sc in StudentClasses on id equals sc.StudentID into jsc
  join st in StudentTeachers on id equals st.StudentID into jst
  where jst.Any() ^ jsc.Any() //exclusive OR, so one must be empty

  //this will return the group with the student's teachers, and an empty group
  //   for the student's classes - 
  //   or group of classes, and empty group of teachers
  select new { classes = jsc, teachers = jst };

  //or, if you know that the non-empty group will always have only one element:
  select new { class = jsc.DefaultIfEmpty(), teacher = jst.DefaultIfEmpty() };

请注意,对于完全外部联接,这也可以。省略where子句并使用上面的第一个select,而不是第二个。

相关问题