2017-02-23 71 views
2

我們使用的是實體框架,我們希望執行簡單的LEFT JOIN。如何使用EF編寫此LEFT JOIN查詢

其實,這是我們想在LINQ(可查詢)的SQL:

SELECT 
    cbl.ID as ClaimBatchLine_ID 
    ,cbl.PurchasePrice 
    ,c.* 
    ,ic.DueDate 
    ,ic.Reference 
FROM ClaimBatchLine cbl 
INNER JOIN Claim c ON c.ID = cbl.CLaim_ID 
LEFT JOIN InvoiceClaim ic ON ic.ID = c.ID 
WHERE cbl.ClaimBatch_ID = @claimBatchId 
ORDER BY cbl.ID 
OFFSET (@recordsPerPage*@page) ROWS 
FETCH NEXT @recordsPerPage ROWS ONLY 

我們想出了是這樣的:

from cbl in ClaimBatchLines where cbl.ClaimBatch_ID == 1 
from c in Claims where c.ID == cbl.Claim_ID 
from ic in InvoiceClaims.DefaultIfEmpty() where ic.ID == c.ID 
select new {cbl, c, ic.Reference} 

和產生以下SQL。

SELECT [t0].[ID], 
    [t0].[ClaimBatch_ID], 
    [t0].[Claim_ID], 
    [t0].[PurchasePrice], 
    [t1].[ID] AS [ID2], 
    [t1].[ClaimType_ID], 
    [t1].[Debtor_ID], 
    [t1].[CustomerContractRevision_ID], 
    [t1].[Date], 
    [t1].[CreatedOn], 
    [t1].[GrossAmount], 
    [t1].[OpenAmount], 
    [t1].[IsProcessedByOpenAmountCalculator], 
    [t1].[RowVersion], 
    [t2].[Reference] AS [Reference] 
FROM [ClaimBatchLine] AS [t0] 
    CROSS JOIN [Claim] AS [t1] 
        LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON 1 = 1 
WHERE([t2].[ID] = [t1].[ID]) 
    AND ([t1].[ID] = [t0].[Claim_ID]) 
    AND ([t0].[ClaimBatch_ID] = @p0); 

它產生相同的結果集。這很好。但是,您可以看到LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON 1 = 1不是我們想要的。我希望它會把它翻譯成LEFT JOIN InvoiceClaim ic ON ic.ID = c.ID

我們做錯了什麼?或者,LINQ to SQL只是次優的(就性能而言),而不能理解我們想要的。

編輯: 在LINQPad這導致一些不錯的查詢

from cbl in ClaimBatchLines 
join c in Claims on cbl.Claim_ID equals c.ID 
join ic in InvoiceClaims on c.ID equals ic.ID into g 
from e in g.DefaultIfEmpty() 
where cbl.ClaimBatch_ID == 1 
select new {cbl, c, e.Reference} 

-- Region Parameters 
DECLARE @p0 INT= 1; 
-- EndRegion 
SELECT [t0].[ID], 
    [Columns left out for brevity] 
    [t2].[Reference] AS [Reference] 
FROM [ClaimBatchLine] AS [t0] 
    INNER JOIN [Claim] AS [t1] ON [t0].[Claim_ID] = [t1].[ID] 
    LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON [t1].[ID] = [t2].[ID] 
WHERE [t0].[ClaimBatch_ID] = @p0; 

但加入像這樣的分頁功能時:

(from cbl in ClaimBatchLines 
join c in Claims on cbl.Claim_ID equals c.ID 
join ic in InvoiceClaims on c.ID equals ic.ID into g 
from e in g.DefaultIfEmpty() 
where cbl.ClaimBatch_ID == 1 
select new {cbl, c, e.Reference}) 
.OrderBy(a => a.cbl.ID) 
.Skip(0 * 15000) 
.Take(15000) 

它產生這個'怪物':

-- Region Parameters 
DECLARE @p0 INT= 1; 
DECLARE @p1 INT= 0; 
DECLARE @p2 INT= 15000; 
-- EndRegion 
SELECT [t4].[ID], 
    [Columsn left out for brevity...] 
FROM 
(
    SELECT ROW_NUMBER() OVER(ORDER BY [t3].[ID]) AS [ROW_NUMBER], 
     [Columsn left out for brevity...] 
    FROM 
    (
     SELECT [t0].[ID], 
      [Columsn left out for brevity...] 
     FROM [ClaimBatchLine] AS [t0] 
      INNER JOIN [Claim] AS [t1] ON [t0].[Claim_ID] = [t1].[ID] 
      LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON [t1].[ID] = [t2].[ID] 
    ) AS [t3] 
    WHERE [t3].[ClaimBatch_ID] = @p0 
) AS [t4] 
WHERE [t4].[ROW_NUMBER] BETWEEN @p1 + 1 AND @p1 + @p2 
ORDER BY [t4].[ROW_NUMBER]; 

甚至更​​糟。當我執行同樣的LINT-TO-EF不是通過LINQpad但在使用EF庫的代碼,我得到這個更大的怪物:

SELECT [Project5].[ID] AS [ID], 
    [Columns left out for brevity...] 
    [Project5].[Reference] AS [Reference] 
FROM 
(
    SELECT [Extent1].[ID] AS [ID], 
     [Extent1].[Claim_ID] AS [Claim_ID], 
     [Extent1].[ClaimBatch_ID] AS [ClaimBatch_ID], 
     [Extent1].[PurchasePrice] AS [PurchasePrice], 
     [Join4].[Id1] AS [ID1], 
     [Join4].[ClaimType_ID] AS [ClaimType_ID], 
     [Join4].[Debtor_ID] AS [Debtor_ID], 
     [Join4].[CustomerContractRevision_ID] AS [CustomerContractRevision_ID], 
     [Join4].[Date] AS [Date], 
     [Join4].[GrossAmount] AS [GrossAmount], 
     [Join4].[OpenAmount] AS [OpenAmount], 
     [Join4].[CreatedOn] AS [CreatedOn], 
     [Join4].[IsProcessedByOpenAmountCalculator] AS [IsProcessedByOpenAmountCalculator], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN '2X' 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN '2X0X' 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN '2X1X' 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN '2X2X' 
      ELSE '2X3X' 
     END AS [C1], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS BIT) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN [Join4].[IsAppeared] 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS BIT) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN CAST(NULL AS BIT) 
     END AS [C2], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS TINYINT) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS TINYINT) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN [Join4].[AdjustmentClaimReason_ID] 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN CAST(NULL AS TINYINT) 
     END AS [C3], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN [Join4].[User_ID] 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
     END AS [C4], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN [Join4].[CostClaimAnnouncement_ID] 
     END AS [C5], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS DECIMAL(19, 4)) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS DECIMAL(19, 4)) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS DECIMAL(19, 4)) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN [Join4].[DiscountFactor] 
     END AS [C6], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS DATETIME2) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS DATETIME2) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS DATETIME2) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN [Join4].[DiscountValidTo] 
     END AS [C7], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN [Join4].[AppliedDiscountAdjustmentClaim_ID] 
     END AS [C8], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS INT) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN [Join4].[ExpiredDiscountAdjustmentClaim_ID] 
     END AS [C9], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS VARCHAR(1)) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS VARCHAR(1)) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS VARCHAR(1)) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN CAST(NULL AS VARCHAR(1)) 
      ELSE [Join4].[Reference] 
     END AS [C10], 
     CASE 
      WHEN((NOT(([Join4].[C11] = 1) 
        AND ([Join4].[C11] IS NOT NULL))) 
       AND (NOT(([Join4].[C12] = 1) 
         AND ([Join4].[C12] IS NOT NULL))) 
       AND (NOT(([Join4].[C13] = 1) 
         AND ([Join4].[C13] IS NOT NULL))) 
       AND (NOT(([Join4].[C14] = 1) 
         AND ([Join4].[C14] IS NOT NULL)))) 
      THEN CAST(NULL AS DATETIME2) 
      WHEN(([Join4].[C14] = 1) 
       AND ([Join4].[C14] IS NOT NULL)) 
      THEN CAST(NULL AS DATETIME2) 
      WHEN(([Join4].[C12] = 1) 
       AND ([Join4].[C12] IS NOT NULL)) 
      THEN CAST(NULL AS DATETIME2) 
      WHEN(([Join4].[C11] = 1) 
       AND ([Join4].[C11] IS NOT NULL)) 
      THEN CAST(NULL AS DATETIME2) 
      ELSE [Join4].[DueDate] 
     END AS [C11], 
     [Extent7].[Reference] AS [Reference] 
    FROM [dbo].[ClaimBatchLine] AS [Extent1] 
     INNER JOIN 
    (
     SELECT [Extent2].[Id] AS [Id1], 
      [Columns left out for brevity...] 
     FROM [dbo].[Claim] AS [Extent2] 
      LEFT OUTER JOIN 
     (
     SELECT [Extent3].[Id] AS [Id], 
       [Extent3].[CostClaimAnnouncement_ID] AS [CostClaimAnnouncement_ID], 
       [Extent3].[DiscountFactor] AS [DiscountFactor], 
       [Extent3].[DiscountValidTo] AS [DiscountValidTo], 
       [Extent3].[AppliedDiscountAdjustmentClaim_ID] AS [AppliedDiscountAdjustmentClaim_ID], 
       [Extent3].[ExpiredDiscountAdjustmentClaim_ID] AS [ExpiredDiscountAdjustmentClaim_ID], 
       CAST(1 AS BIT) AS [C1] 
     FROM [dbo].[CostClaim] AS [Extent3] 
    ) AS [Project1] ON [Extent2].[Id] = [Project1].[Id] 
      LEFT OUTER JOIN 
     (
     SELECT [Extent4].[Id] AS [Id], 
       [Extent4].[IsAppeared] AS [IsAppeared], 
       CAST(1 AS BIT) AS [C1] 
     FROM [dbo].[InterestClaim] AS [Extent4] 
    ) AS [Project2] ON [Extent2].[Id] = [Project2].[Id] 
      LEFT OUTER JOIN 
     (
     SELECT [Extent5].[Id] AS [Id], 
       [Extent5].[AdjustmentClaimReason_ID] AS [AdjustmentClaimReason_ID], 
       [Extent5].[User_ID] AS [User_ID], 
       CAST(1 AS BIT) AS [C1] 
     FROM [dbo].[AdjustmentClaim] AS [Extent5] 
    ) AS [Project3] ON [Extent2].[Id] = [Project3].[Id] 
      LEFT OUTER JOIN 
     (
     SELECT [Extent6].[Id] AS [Id], 
       [Extent6].[Reference] AS [Reference], 
       [Extent6].[DueDate] AS [DueDate], 
       CAST(1 AS BIT) AS [C1] 
     FROM [dbo].[InvoiceClaim] AS [Extent6] 
    ) AS [Project4] ON [Extent2].[Id] = [Project4].[Id] 
    ) AS [Join4] ON [Extent1].[Claim_ID] = [Join4].[Id1] 
     LEFT OUTER JOIN [dbo].[InvoiceClaim] AS [Extent7] ON [Join4].[Id1] = [Extent7].[Id] 
    WHERE 1 = [Extent1].[ClaimBatch_ID] 
) AS [Project5] 
ORDER BY [Project5].[ID] ASC 
OFFSET 0 ROWS FETCH NEXT 15000 ROWS ONLY; 

這到底是怎麼回事呢?在LINQpad上搔抓最初看起來很好。但是生產代碼中的最後一個查詢太簡單了! 也許這些查詢適用於一些簡單的應用程序。在查詢更多記錄中的500k條記錄時,這對我來說並不好。我將堅持使用簡單的SQL表值函數,而不是使用LINQ。太遺憾了。

回答

2

試試這個方法:

from cbl in ClaimBatchLines 
join c in Claims on c.ID equals cbl.Claim_ID 
join ic in InvoiceClaims on ic.ID equals c.ID into g 
from e in g.DefaultIfEmpty() 
where cbl.ClaimBatch_ID == 1 
select new {cbl, c, e.Reference} 

有關如何執行左鍵更多信息加入LINQ看看這個link

+0

其實沒有必要檢查L2E查詢中的「null」,因爲SQL自然會處理這個問題。唯一需要注意的是屬性是值類型,在這種情況下,您需要將其提升(轉換)爲相應的可空類型。 –

+1

明白了。感謝@IvanStoev,每天我都會學到新的東西;) – octavioccl

+1

我們都這樣做,並且用EF核心應該重新開始,因爲它完全改變了一切:) –

1

嘗試使用LINQ的join寫它。

var q = 
(from cbl in ClaimBatchLines 
join c in Claims on cbl.Claim_ID equals c.ID 
join tmpIc in InvoiceClaims on c.ID equals tmpIc.ID into g 
from ic in g.DefaultIfEmpty() 
where cbl.ClaimBatch_ID == 1 
select new { cbl, c, ic }) 
    .OrderBy(x => x.cbl.ID) 
    .Skip(recordsPerPage * page) 
    .Take(recordsPerPage); 
+0

這會導致與從子查詢中選擇InvoiceClaim表中的列的子集的LEFT JOIN。我不確定它是否會與已接受的答案產生巨大的性能差異。但ClaimBatches可以很大(500K記錄),SQL越簡單,我就越好。感謝您的幫助。 –

+0

+1,你在那裏完成訂購,跳過和服用。我不知道查詢可以用()和(與其他LINQ方法結合使用)劃分。 –

2

最簡單的方法就是你的地方移動到一起:

from cbl in ClaimBatchLines where cbl.ClaimBatch_ID == 1 
from c in Claims where c.ID == cbl.Claim_ID 
from ic in InvoiceClaims.Where(x => x.ID == c.ID).DefaultIfEmpty() 
select new {cbl, c, ic.Reference} 

這將使得查詢的InvoiceClaims表使用的left join

+0

這會對索賠產生一個'CROSS JOIN'。 ClaimBatchLine總是與Claim聲明1:1的關係。從我從這篇文章中瞭解到的:http://stackoverflow.com/questions/17759687/cross-join-vs-inner-join-in-sql-server-2008是使用INNER JOIN是首選的,當有故意兩張表之間的關係。但根據這篇文章http://stackoverflow.com/a/6418253/1567665表現是相同的。不是被接受的答案。但+1正確的答案。 –