假设我有一个左外连接,如下所示:
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 }
如何使用扩展方法表达相同的任务?如。
Foo.GroupJoin(Bar, f => f.Foo_Id, b => b.Foo_Id, (f,b) => ???)
.Select(???)
对于一个表Bar与一个表Foo对Foo的连接(左外)。Foo_Id = Bar。Foo_Id用lambda表示:
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});
虽然接受的答案工作,是很好的Linq对象它困扰我的SQL查询不只是一个直接的左外连接。
下面的代码依赖于允许您传递表达式并将它们调用到查询的LinqKit项目。
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});
改进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() });
否则,您在join中获得的行将是随机的(或者更具体地说,无论db碰巧先找到哪一行)。
要实现两个数据集的连接,不需要使用组连接方法。
内连接:
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,最好使用GroupJoin,因为它在内部使用Lookup。但是如果您正在查询DB,那么跳过GroupJoin是AFAIK作为性能。
Personlay对我来说,这种方式比GroupJoin()更具可读性。
对我来说更简单。
var appuser = appUsers.GroupJoin(trackLogin, u => u.Id, ur => ur.UserId, (u, ur) => new { u = u, ur = ur })
.Select( m => new { m.u.Id, m.u.Email, m.u.IsSuperUser, m.u.RoleId,
LastLogin = m.ur.Select(t => t.LastLogin).FirstOrDefault()}).ToList();
Marc Gravell的答案变成了一个支持IQueryable<T>接口的扩展方法,并添加了对c# 8.0 NRT的支持,如下所示:
#nullable enable
using LinqKit;
using LinqKit.Core;
using System.Linq.Expressions;
...
/// <summary>
/// Left join queryable. Linq to SQL compatible. IMPORTANT: any Includes must be put on the source collections before calling this method.
/// </summary>
public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
this IQueryable<TOuter> outer,
IQueryable<TInner> inner,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner?, TResult>> resultSelector)
{
return outer
.AsExpandable()
.GroupJoin(
inner,
outerKeySelector,
innerKeySelector,
(outerItem, innerItems) => new { outerItem, innerItems })
.SelectMany(
joinResult => joinResult.innerItems.DefaultIfEmpty(),
(joinResult, innerItem) =>
resultSelector.Invoke(joinResult.outerItem, innerItem));
}