Левое внешнее соединение LINQ to SQL

142

Эквивалентен ли этот запрос LEFT OUTERсоединению?

//assuming that I have a parameter named 'invoiceId' of type int
from c in SupportCases
let invoice = c.Invoices.FirstOrDefault(i=> i.Id == invoiceId)
where (invoiceId == 0 || invoice != null)    
select new 
{
      Id = c.Id
      , InvoiceId = invoice == null ? 0 : invoice.Id
}
Али Казми
источник

Ответы:

168

Не совсем так - поскольку каждая «левая» строка в левом-внешнем-соединении будет соответствовать 0-n «правым» строкам (во второй таблице), тогда как ваша соответствует только 0-1. Чтобы выполнить левое внешнее соединение, вам понадобится SelectManyи DefaultIfEmpty, например:

var query = from c in db.Customers
            join o in db.Orders
               on c.CustomerID equals o.CustomerID into sr
            from x in sr.DefaultIfEmpty()
            select new {
               CustomerID = c.CustomerID, ContactName = c.ContactName,
               OrderID = x == null ? -1 : x.OrderID };   

( или через методы расширения )

Марк Гравелл
источник
21
Может кто-нибудь объяснить, как работает этот сумасшедший синтаксис? Я не вижу, как какое-либо из этих ключевых слов волшебным образом превращает его в левое соединение. Что делает "into sr"? Linq иногда меня расстраивает :)
Джо Филлипс
2
@JoePhillips У меня большой опыт работы с SQL, но пытаться изучить LINQ - все равно что идти по грязи. Согласен, это безумие.
Nick.McDermaid
@ marc-gravell: Не могли бы вы помочь мне решить мой запрос sql для преобразования linq: stackoverflow.com/questions/28367941/…
Vishal I Patil
@VishalIPatil, почему вы хотите конвертировать из SQL в LINQ? SQL работает отлично, гораздо более предсказуем и эффективен ...
Марк Грейвелл
1
@VishalIPatil так ... зачем это делать? Практически каждый инструмент LINQ включает возможность выполнения написанного вручную SQL. Почему бы просто не сделать это?
Марк Грейвелл
220

Вам не нужны операторы в:

var query = 
    from customer in dc.Customers
    from order in dc.Orders
         .Where(o => customer.CustomerId == o.CustomerId)
         .DefaultIfEmpty()
    select new { Customer = customer, Order = order } 
    //Order will be null if the left join is null

И да, приведенный выше запрос действительно создает соединение LEFT OUTER.

Ссылка на аналогичный вопрос, который обрабатывает несколько левых соединений: Linq to Sql: несколько левых внешних соединений

Амир
источник
14
Хотя я знаю, что ответ @Marc Gravvel действительно работает, я действительно предпочитаю этот метод, потому что IMO он больше соответствует тому, как должно выглядеть левое соединение.
llaughlin 06
1
Отличный ответ. Ищу более 5 часов поиска в гугле. Это единственный способ, которым в результате SQL останется соединение.
Faisal Mq
1
СПАСИБО ... Я искал решение для этого весь день, и ваш код прибил его (и кажется естественным для загрузки). Хотел бы я проголосовать за это несколько раз.
Джим
2
@ Джим, спасибо :-) Я рад, что разработчики до сих пор извлекают выгоду из этого ответа. Я полностью согласен с тем, что DefaultIfEmpty () кажется намного более естественным, чем использование операторов into.
Amir
7
Просто примечание для всех, кто обнаружит это, как я только что, это приводит к LEFT OUTER JOIN внутри CROSS APPLY, что означает, что вы получите дубликаты, если в правой части соединения будет несколько совпадений. Решение Марка Гравелла, хотя и не такое "красивое", дало мне правильный вывод SQL и набор результатов, которые я искал.
Mike U
13
Public Sub LinqToSqlJoin07()
Dim q = From e In db.Employees _
        Group Join o In db.Orders On e Equals o.Employee Into ords = Group _
        From o In ords.DefaultIfEmpty _
        Select New With {e.FirstName, e.LastName, .Order = o}

ObjectDumper.Write(q) End Sub

Проверьте http://msdn.microsoft.com/en-us/vbasic/bb737929.aspx

Кришнарадж Барватхайя
источник
Хорошая попытка, но похоже, что OP использует C #. Синтаксис VB странным образом отличается.
Левитикон 06
5

Я нашел 1 решение. если вы хотите перевести этот тип SQL (левое соединение) в Linq Entity ...

SQL:

SELECT * FROM [JOBBOOKING] AS [t0]
LEFT OUTER JOIN [REFTABLE] AS [t1] ON ([t0].[trxtype] = [t1].[code])
                                  AND ([t1]. [reftype] = "TRX")

LINQ:

from job in JOBBOOKINGs
join r in (from r1 in REFTABLEs where r1.Reftype=="TRX" select r1) 
          on job.Trxtype equals r.Code into join1
from j in join1.DefaultIfEmpty()
select new
{
   //cols...
}
мокт
источник
См. Этот комментарий , сущности Linq-to-SQL не поддерживают DefaultIfEmpty.
TJ Crowder
2

Хочу добавить еще кое-что. В LINQ to SQL, если ваша БД правильно построена и ваши таблицы связаны через ограничения внешнего ключа, вам вообще не нужно выполнять соединение.

Используя LINQPad, я создал следующий запрос LINQ:

//Querying from both the CustomerInfo table and OrderInfo table
from cust in CustomerInfo
where cust.CustomerID == 123456
select new {cust, cust.OrderInfo}

Что было переведено в (слегка усеченный) запрос ниже

 -- Region Parameters
 DECLARE @p0 Int = 123456
-- EndRegion
SELECT [t0].[CustomerID], [t0].[AlternateCustomerID],  [t1].[OrderID], [t1].[OnlineOrderID], (
    SELECT COUNT(*)
    FROM [OrderInfo] AS [t2]
    WHERE [t2].[CustomerID] = [t0].[CustomerID]
    ) AS [value]
FROM [CustomerInfo] AS [t0]
LEFT OUTER JOIN [OrderInfo] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[CustomerID], [t1].[OrderID]

Обратите внимание на LEFT OUTER JOINвышеизложенное.

Брайан Кремер
источник
1

Позаботьтесь о производительности:

Я испытал, что, по крайней мере, с EF Core разные ответы, приведенные здесь, могут привести к разной производительности. Мне известно, что OP спрашивал о Linq to SQL, но мне кажется, что те же вопросы возникают и с EF Core.

В конкретном случае, с которым мне пришлось справиться, (синтаксически более приятное) предложение Марка Гравелла привело к левым соединениям внутри перекрестного применения - аналогично тому, что описал Майк У. - в результате чего предполагаемые затраты на этот конкретный запрос составили два в разы выше по сравнению с запросом без перекрестных соединений . Время выполнения сервера отличалось в 3 раза . [1]

Решение Марка Гравелла привело к запросу без перекрестных соединений.

Контекст: мне, по сути, нужно было выполнить два левых соединения для двух таблиц, каждая из которых снова требовала соединения с другой таблицей. Кроме того, там мне пришлось указать другие условия для таблиц, к которым мне нужно было применить левое соединение. Вдобавок у меня было два внутренних соединения на главной таблице.

Ориентировочные расходы оператора:

  • с крестом применить: 0,2534
  • без креста применить: 0,0991.

Время выполнения сервером в мс (запросы выполнялись 10 раз; измерено с помощью SET STATISTICS TIME ON):

  • с крестовым нанесением: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • без крестовины: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(Самый первый запуск был медленнее для обоих запросов; кажется, что что-то кешируется.)

Размеры стола:

  • основная таблица: 87 строк,
  • первая таблица для левого соединения: 179 строк;
  • вторая таблица для левого соединения: 7 строк.

Версия EF Core: 2.2.1.

Версия SQL Server: MS SQL Server 2017-14 ... (в Windows 10).

Все соответствующие таблицы имели индексы только по первичным ключам.

Мой вывод: всегда рекомендуется смотреть на сгенерированный SQL, поскольку он действительно может отличаться.


[1] Интересно, что при включении «Клиентской статистики» в MS SQL Server Management Studio я мог увидеть противоположную тенденцию; а именно, последний запуск решения без перекрестного применения занял более 1 секунды. Полагаю, что здесь что-то пошло не так - может быть, с моей настройкой.

Андреас Шютц
источник