你如何使用linq扩展方法执行左外连接
假设我有一个左外连接,如下所示:
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 }
我将如何使用扩展方法来expression相同的任务? 例如
Foo.GroupJoin(Bar, f => f.Foo_Id, b => b.Foo_Id, (f,b) => ???) .Select(???)
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});
由于这似乎是左外连接使用方法(扩展)语法事实上的问题,我想我会添加一个替代目前select的答案,(至less在我的经验)已经更常见的是我后
// 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 });
使用一个简单的数据集显示差异(假设我们正在join这些值本身):
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对于典型的左外部连接定义是正确的,但正如前面提到的,根据数据集的不同,这通常是非常复杂的。
实现两个数据集的连接不需要Group Join方法。
内部联接:
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正确转换为SQL。 对于LINQ to对象,使用GroupJoin进行连接是因为它在内部使用Lookup,但是如果您正在查询DB,那么跳过GroupJoin就是AFAIK。
与GroupJoin()相比,Personlay对于我来说更具可读性。SelectMany()
您可以创build扩展方法,如:
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的答案,如果你有一个表,你留在外面join你只想要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() });
否则,你在连接中得到的哪一行将是随机的(或者更具体地说,无论哪个db首先发现)。