2010-01-18 41 views
36

我已经继承了一个没有精确设计的数据库,我需要处理一些数据。让我举之类的话的一种较为常见的比喻我必须做的:如何在Linq中完成一个完整的外连接?

比方说,我们有一个Student表,所有他参加了类的StudentClass表记录保存和存储所有的教师StudentTeacher表谁教这个学生。是的,我知道这是一个愚蠢的设计,将老师存储在班级表上会更有意义 - 但这正是我们正在处理的内容。

我现在想要清理数据,我想找到学生有老师但没有班级,或班级但没有老师的所有地方。 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怎么做?

+11

注意:这不是一个完整的外部联接 - 因为您想要排除内部联接成功的行。我只是提到这一点,因为这是'full outer join linq'的顶级搜索结果 - 所以如果这就是某人寻找的那么答案可能不对 –

+1

它可能是一个高性能解决方案,请参阅下面的问题。 http:// stackoverflow。com/questions/5489987/linq-full-outer-join/8501701#8501701 – vrluckyin

回答

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

你可以这两个语句可能挤入一个,但我想你” d牺牲代码清晰度。

+3

设置联合会自动使事情不同http://en.wikipedia.org/wiki/Union_(set_theory) –

+0

@VisionarySoftwareSolutions正确的你 - 回答编辑 –

+0

非常感谢你! –

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/中查看样本 好工具,

+0

富有创意,但并不像我希望的那样高雅。我会给你+1链接到LinqPad,它看起来像一个非常酷的软件。 :) –

+0

;-))你在LinqPad中有更多优雅的例子 它有一个很酷的数据库连接,你有可能链接到你的dll:s等等...... 作者还写了最好的书C# http://www.youtube.com/watch?v=Z6-iUNfJsJw&feature=channel – salgo60

+3

需要注意的两点:1)这会产生一个LEFT OUTER JOIN而不是一个完整的外连接,2)st == null检查不是需要在linq-to-sql中,而不是你可以做st.StudentID? “(没有StudentTeacher)” –

18

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

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

如果和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; 

这里是使用的毗连()一个行选项:

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

这是一个很好的问题语义声明,但它没有帮助,因为要在Linq中工作,a和b必须是相同的类型,这不是这里的情况。 –

+0

这是错误的。正确的外部联接语句可以在这里找到:http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx。空值检查不是必需的,必须引入另一个变量以从组中选择 –

+1

@grzegorz_p msdn示例显示LEFT外部加入。问题是关于完全外连接 –

17

扩展方法:

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

+1为扩展方法的概念!我有一种感觉,它可以在内部进行优化,但仍然是一个很好的答案。 –

1

基于沙乌尔的答案,但有一点精简:

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,而不是第二个。