LINQ до SQL зліва назовні


140

Цей запит еквівалентний 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
}

Відповіді:


167

Не зовсім - оскільки кожен "лівий" рядок у ліво-зовнішньому з'єднанні буде відповідати 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 };   

( або за допомогою методів розширення )


19
Чи може хтось пояснити, як працює цей божевільний синтаксис? Я не бачу, як будь-яке з цих ключових слів магічно робить його лівим з’єднанням. Що робить "у sr"? Лінк часом мене засмучує :)
Джо Філіпс

2
@JoePhillips У мене є багато досвіду SQL, але намагатися навчитися LINQ - це як перебирати грязь. Я згоден, це абсолютно божевільно.
Nick.McDermaid

@ Марк-Gravell: Чи не могли б ви мені допомогти у вирішенні моєї SQL запит для перетворення Linq: stackoverflow.com/questions/28367941 / ...
Вишал я Patil

@VishalIPatil чому ви хочете конвертувати з SQL в LINQ? SQL працює чудово і є набагато більш передбачуваним та ефективним ...
Marc Gravell

1
@VishalIPatil так ... навіщо це робити? Практично кожен інструмент LINQ включає можливість запуску рукописного SQL. Чому б просто не зробити це?
Марк Гравелл

216

Вам не потрібно вводити заяви:

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, він відчуває себе більше у відповідності з тим, як повинен виглядати лівий приєднання.
Люфлін

1
Відмінна відповідь. Шукаєте понад 5 годин пошуку в Google. Це єдиний спосіб, коли в результаті SQL залишиться в ньому приєднатися.
Faisal Mq

1
ДЯКУЮ вам так багато .... Я шукав рішення для цього весь день і ваш код прибив його (і він вважає себе природним для завантаження). Бажаю, щоб я міг підтвердити це кілька разів.
Джим

2
@ Джим спасибі :-) Я радий, що дивовижі все ще отримують пробіг від цієї відповіді. Я повністю погоджуюся, що DefaultIfEmpty () відчуває себе набагато природніше, ніж використання операторів into.
Амір

7
Лише зауваження для тих, хто вважає це, як я щойно робив, це призводить до ВИГЛЯДУВАННЯ ВЛІСЬКОГО ВНУТРІШНЯ у КРЕСЬОМУ ЗАЯВКІ, що означає, що ви отримаєте дублікати, якщо в правій частині поєднання буде кілька матчів. Рішення Марка Гравелла, хоча і не настільки "симпатичне", дало мені належний вихід SQL та набір результатів, який я шукав.
Майк У

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


Хороша спроба, але схоже, що ОП використовує c #. Синтаксис VB дивно відрізняється.
Левитикон

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-SQL не підтримують DefaultIfEmpty.
TJ Crowder

2

Я хотів би додати ще одну річ. У LINQ до 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 різні відповіді, що даються тут, можуть призвести до різної продуктивності. Мені відомо, що ОП запитала про Linq у SQL, але мені здається, що такі ж питання виникають і з EF Core.

У конкретному випадку, з яким мені довелося впоратися, пропозиція (синтаксично приємнішого) Марка Гравелла призвела до того, що ліві з’єднання всередині хреста застосовуються - аналогічно тому, що описав Майк У, - що призвело до того, що орієнтовні витрати на цей конкретний запит були дві разів вищий у порівнянні з запитом без перехресного з'єднання . Час виконання сервера відрізнявся в 3 рази . [1]

Рішення Marc Gravell призвело до запиту без перехресних з'єднань.

Контекст: По суті, мені потрібно було виконати два лівих з'єднання на двох таблицях, кожна з яких знову вимагала приєднання до іншої таблиці. Крім того, там мені довелося вказати інші умови, де в таблицях, на яких мені потрібно було застосувати ліве з'єднання. Крім того, у мене було два внутрішніх приєднання на головному столі.

Орієнтовні витрати оператора:

  • з хрестом застосовувати: 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 с. Я гадаю, що тут щось не так - можливо, з моїм налаштуванням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.