C# 如何使用EF编写这个左连接查询

C# 如何使用EF编写这个左连接查询,c#,entity-framework,tsql,linq-to-entities,left-join,C#,Entity Framework,Tsql,Linq To Entities,Left Join,我们正在使用实体框架,我们希望执行一个简单的左连接 事实上,这就是我们希望在LINQ中使用的SQL(可查询): 我们得出的结论是: 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, i

我们正在使用实体框架,我们希望执行一个简单的左连接

事实上,这就是我们希望在LINQ中使用的SQL(可查询):

我们得出的结论是:

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);
它生成相同的结果集。那太好了。但是,正如您所看到的,1=1上的左外部联接[InvoiceClaim]作为[t2]并不是我们想要的。我希望它能把它翻译成
左连接InvoiceClaim ic ON ic.ID=c.ID

我们做错什么了吗?或者LINQtoSQL只是次优的(性能方面的)而不能理解我们想要什么

编辑: 在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];
甚至更糟。当我不是通过LINQpad而是在使用EF存储库的代码中对EF执行相同的LINT时,我得到了一个更大的怪物:

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。真遗憾。

试着这样做:

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中执行左连接的更多信息,请查看此

尝试使用linq的
连接编写它

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);

最简单的方法是将您的连接移动到何处:

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
表上使用
左连接
,实际上在L2E查询中不需要检查
null
,因为SQL自然会处理该连接。唯一需要注意的是属性是否为值类型,在这种情况下,需要将其升级(强制转换)为相应的可为null的类型。明白了。感谢@IvanStoev提供的信息,每天我都能学到新东西;)我们都这样做了,EF Core应该重新开始,因为它完全改变了一切:)我必须交换cbl.Claim_ID等于c.ID,因为我收到了消息
CS1937,名称“c”不在“equals”左侧的范围内这个答案产生了最简洁、最清晰的TSQL。声明上的内部联接和InvoiceClaim表上的左联接。非常感谢您的回答。这将导致一个左连接和一个子查询,该子查询从InvoiceClaim表中选择列的子集。我不确定这是否会与公认的答案产生巨大的性能差异。但是ClaimBatches可能很大(500K条记录),我认为SQL越简单越好。感谢您的帮助。+1感谢您如此完整地订购、跳过和服用。我不知道查询可以用()来划分,并与其他LINQ方法结合使用。这会在声明中产生一个
交叉连接。其中ClaimBatchLine始终与索赔有1:1的关系。从这篇文章中我了解到:当两个表之间存在有意的关系时,最好使用内部联接。但根据这篇文章,表现是一样的。这不是公认的答案。但是+1表示正确答案。
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}