Подтвердить что ты не робот

Linq - левое соединение в нескольких (OR) условиях

Мне нужно сделать левое соединение на нескольких условиях, где условия OR, а не AND s. Я нашел множество образцов последнего, но изо всех сил стараюсь получить правильный ответ для моего сценария.

from a in tablea
join b in tableb on new { a.col1, a.col2 } equals new { b.col1, b.col2 }
group a by a into g
select new () { col1 = a.col1, col2 = a.col2, count = g.Count() }

отлично подходит для объединений, где должны соответствовать все условия. Мне нужно, чтобы соединение соответствовало on a.col1 = b.col1 OR a.col2 = b.col2.

Я знаю, что это должно быть легко, но я не понимаю этого!

Edit:

Чтобы дать немного больше информации, цель запроса состоит в том, чтобы получить проекцию, содержащую все поля из "a", плюс количество совпадающих записей в "b". Я изменил пример выше, чтобы попытаться проиллюстрировать, что мне нужно. Когда я запускаю с помощью вышеописанного подхода, Джон Скит заметил, что я получаю подсчет всех записей от a, а не счет связанных записей в b.

Основное левое соединение отлично работает:

from a in tablea
from b in tableb
.Where( b => ( a.col1 == b.col1 || a.col2 == b.col2))
.DefaultIfEmpty()
select new { col1 = a.col1, col2 = a.col2 }

Если я пересмотрю его, чтобы добавить группировку, как показано ниже

from a in tablea
from b in tableb
.Where( b => ( a.col1 == b.col1 || a.col2 == b.col2))
.DefaultIfEmpty()
group a by a.col1 into g
select new { col1 = g.Key, count = g.Count() }

Я получаю количество записей, возвращаемых из a, а не количество записей в совпадении в b.

Edit:

Я дам ответ Джону - я решил проблему с подсчетом - я не понял, что могу использовать ламду для фильтрации счета (g.Count(x => x != null)). Кроме того, мне нужно группировать букву "а", а не "а", как было выше. Это дает правильный результат, но SQL не так эффективен, как я бы написал его вручную, поскольку он добавляет коррелированный подзапрос - если кто-нибудь может посоветовать лучший способ написать его, чтобы имитировать следующий SQL, я был бы признателен!

select a.col1, count(b.col1)
from tablea a
left join tableb b
on a.col1 = b.col1
or a.col2 = b.col2
group by a.col1
4b9b3361

Ответ 1

LINQ напрямую поддерживает equijoins. Если вы хотите сделать какой-либо другой вид соединения, вам в основном нужно перекрестное соединение и where:

from a in tablea
from b in tableb
where a.col1 == b.col1 || a.col2 == b.col2
select ...

Вероятно, стоит проверить, как выглядит сгенерированный SQL и как выглядит план запроса. Там могут быть более эффективные способы сделать это, но это, вероятно, самый простой подход.

Ответ 2

В зависимости от поставщика запроса вы можете просто выбрать два из предложений:

from a in tablea
from b in tableb 
where a.col1 == b.col1 || a.col2 == b.col2

Что, если вы выполняете на БД, будет столь же эффективным. Если вы выполняете внутреннюю память (Linq to Objects), это будет перечислять все возможные комбинации, которые могут быть неэффективными.

Arg, Skeeted; -).

Возможны более эффективные альтернативы Linq to Objects. Оператор join перечисляет каждый источник только один раз, а затем делает хеш-соединение, поэтому вы можете разделить or-clause на два отдельных объединения, а затем принять их союз. Объединение в linq - это просто конкатенация без дубликатов, так что это выглядит следующим образом:

(from a in tablea
join b in tableb on a.Col1 equals b.Col1
select new {a, b})
.Concat(
from a in tablea
join b in tableb on a.Col2 equals b.Col2
select new {a, b}
).Distinct()

Этот подход работает, и это всего лишь один запрос, но он несколько неочевиден в том смысле, что характеристики производительности кода зависят от детального понимания того, как работает linq. Лично, если вы хотите сделать хеш-соединение с потенциально множественными совпадениями, более очевидным инструментом является ToLookup. Альтернативный вариант может выглядеть следующим образом:

var bBy1 = tableb.ToLookup(b=>b.Col1);
var bBy2 = tableb.ToLookup(b=>b.Col2);
var q3 = 
    from a in tablea
    from b in bBy1[a.Col1].Concat(bBy2[a.Col2]).Distinct()
    ...

Это решение на самом деле короче, и причина, по которой он работает, более очевидна, поэтому я бы предпочел. Просто помните, что если вы разделите оператор || на два отдельных запроса, например, в приведенных выше двух сценариях, которые вам нужно вручную избежать двойного подсчета результатов (т.е. Использовать Distinct).