Как вы выполняете левое внешнее соединение с использованием методов расширения 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 }
Как я могу выразить ту же задачу с помощью методов расширения? Например.
Foo.GroupJoin(Bar, f => f.Foo_Id, b => b.Foo_Id, (f,b) => ???)
.Select(???)
Ответы
Ответ 1
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});
Ответ 2
Поскольку это, по-видимому, является де-факто SO-вопросом для левых внешних объединений с использованием синтаксиса метода (расширения), я думал, что добавлю альтернативу выбранному в данный момент ответу, который (по моему опыту, по крайней мере) был более общим, чем Я после
// 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 });
Чтобы отобразить разницу, используя простой набор данных (при условии, что мы присоединяемся к самим значениям):
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 соответствует типичному определению левого внешнего соединения, но, как я упоминал ранее, часто излишне сложно в зависимости от набора данных.
Ответ 3
Метод объединения групп не требуется для объединения двух наборов данных.
Внутренняя регистрация:
var qry = Foos.SelectMany
(
foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id),
(foo, bar) => new
{
Foo = foo,
Bar = bar
}
);
Для Left Join просто добавьте 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 для объектов он объединяется с помощью GroupJoin, поскольку он внутренне использует Lookup, но если вы запрашиваете DB, то пропущение GroupJoin является AFAIK как исполнителем.
Personlay для меня таким образом более читабельен по сравнению с GroupJoin(). SelectMany()
Ответ 4
Вы можете создать метод расширения, например:
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);
}
Ответ 5
Улучшение ответа 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() });
В противном случае какая строка, которую вы получите в соединении, будет случайной (или, более конкретно, в зависимости от того, что db обнаруживает первым).
Ответ 6
Повернув Marc Gravell, ответьте на метод расширения, я сделал следующее.
internal static IEnumerable<Tuple<TLeft, TRight>> LeftJoin<TLeft, TRight, TKey>(
this IEnumerable<TLeft> left,
IEnumerable<TRight> right,
Func<TLeft, TKey> selectKeyLeft,
Func<TRight, TKey> selectKeyRight,
TRight defaultRight = default(TRight),
IEqualityComparer<TKey> cmp = null)
{
return left.GroupJoin(
right,
selectKeyLeft,
selectKeyRight,
(x, y) => new Tuple<TLeft, IEnumerable<TRight>>(x, y),
cmp ?? EqualityComparer<TKey>.Default)
.SelectMany(
x => x.Item2.DefaultIfEmpty(defaultRight),
(x, y) => new Tuple<TLeft, TRight>(x.Item1, y));
}
Ответ 7
Хотя принятый ответ работает и хорош для Linq to Objects, он прослушивал меня, что SQL-запрос - это не просто прямая левая внешняя связь.
Следующий код основан на LinkKit Project, который позволяет передавать выражения и вызывать их в ваш запрос.
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});
Ответ 8
Существует простое решение для этого
Просто используйте .HasValue в вашем выборе
.Select(s => new
{
FooName = s.Foo_Id.HasValue ? s.Foo.Name : "Default Value"
}
Очень просто, нет необходимости в групповом присоединении или чем-либо еще