如何让 EF6 生成高效的 in(...) 查询

How to get EF6 to generate efficient in(...) queries

所以...EF6 正在创建一个非常低效的查询。我有一个针对具有三种不同类型地址的数据源的查询。我有一个地址 ID 列表,这些 ID 可能与用户尝试使用的新地址重复。理想情况下,我希望此查询检查几个地址 ID 中的任何一个是否在给定的一组提供的 ID 中。当前查询:

return await _tickets.Where(t =>
    t.Metadata is SIFTEscalationMetadata && (
        addesses.Any(a => a == (t.Metadata as SIFTEscalationMetadata).Address.Id) ||
        addesses.Any(a => a == (t.Metadata as SIFTEscalationMetadata).AddressEntered.Id) ||
        addesses.Any(a => a == (t.Metadata as SIFTEscalationMetadata).CleanedAddress.Id))).ToArrayAsync();

正在变成这样:

SELECT 
    [Project1].[TicketId] AS [TicketId], 
    [Project1].[TicketType] AS [TicketType], 
    [Project1].[Opened] AS [Opened], 
    [Project1].[Closed] AS [Closed], 
    [Project1].[Modified] AS [Modified], 
    [Project1].[EscalationStatusText] AS [EscalationStatusText], 
    [Project1].[QualificationStatusText] AS [QualificationStatusText], 
    [Project1].[ProductsText] AS [ProductsText], 
    [Project1].[Cancelled] AS [Cancelled], 
    [Project1].[CancellationReason_Id] AS [CancellationReason_Id], 
    [Project1].[CreatedBy_Id] AS [CreatedBy_Id], 
    [Project1].[Metadata_Id] AS [Metadata_Id], 
    [Project1].[NotesContainer_Id] AS [NotesContainer_Id]
    FROM ( SELECT 
        [Extent1].[TicketId] AS [TicketId], 
        [Extent1].[TicketType] AS [TicketType], 
        [Extent1].[Opened] AS [Opened], 
        [Extent1].[Closed] AS [Closed], 
        [Extent1].[Modified] AS [Modified], 
        [Extent1].[EscalationStatusText] AS [EscalationStatusText], 
        [Extent1].[QualificationStatusText] AS [QualificationStatusText], 
        [Extent1].[ProductsText] AS [ProductsText], 
        [Extent1].[Cancelled] AS [Cancelled], 
        [Extent1].[CancellationReason_Id] AS [CancellationReason_Id], 
        [Extent1].[CreatedBy_Id] AS [CreatedBy_Id], 
        [Extent1].[Metadata_Id] AS [Metadata_Id], 
        [Extent1].[NotesContainer_Id] AS [NotesContainer_Id], 
        CASE WHEN ([Extent2].[TicketMetadataID] IS NULL) THEN CAST(NULL AS varchar(1)) ELSE '2X0X' END AS [C1]
        FROM  [dbo].[Tickets] AS [Extent1]
        LEFT OUTER JOIN [dbo].[TicketMetadata] AS [Extent2] ON ([Extent2].[Discriminator] = N'SIFTEscalationMetadata') AND ([Extent1].[Metadata_Id] = [Extent2].[TicketMetadataID])
    )  AS [Project1]
    WHERE ([Project1].[C1] LIKE '2X0X%') AND (( EXISTS (SELECT 
        1 AS [C1]
        FROM   (SELECT 
            486524 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            486525 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            486526 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]
        UNION ALL
            SELECT 
            508376 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable4]
        UNION ALL
            SELECT 
            508377 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable5]
        UNION ALL
            SELECT 
            508378 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable6]) AS [UnionAll5]
        LEFT OUTER JOIN  (SELECT 
            [Extent3].[Address_Id] AS [Address_Id], 
            '2X0X' AS [C1]
            FROM [dbo].[TicketMetadata] AS [Extent3]
            WHERE ([Extent3].[Discriminator] = N'SIFTEscalationMetadata') AND ([Project1].[Metadata_Id] = [Extent3].[TicketMetadataID]) ) AS [Project8] ON 1 = 1
        WHERE [UnionAll5].[C1] = (CASE WHEN ([Project8].[C1] LIKE '2X0X%') THEN [Project8].[Address_Id] END)
    )) OR ( EXISTS (SELECT 
        1 AS [C1]
        FROM   (SELECT 
            486524 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable7]
        UNION ALL
            SELECT 
            486525 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable8]
        UNION ALL
            SELECT 
            486526 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable9]
        UNION ALL
            SELECT 
            508376 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable10]
        UNION ALL
            SELECT 
            508377 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable11]
        UNION ALL
            SELECT 
            508378 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable12]) AS [UnionAll10]
        LEFT OUTER JOIN  (SELECT 
            [Extent4].[AddressEntered_Id] AS [AddressEntered_Id], 
            '2X0X' AS [C1]
            FROM [dbo].[TicketMetadata] AS [Extent4]
            WHERE ([Extent4].[Discriminator] = N'SIFTEscalationMetadata') AND ([Project1].[Metadata_Id] = [Extent4].[TicketMetadataID]) ) AS [Project16] ON 1 = 1
        WHERE [UnionAll10].[C1] = (CASE WHEN ([Project16].[C1] LIKE '2X0X%') THEN [Project16].[AddressEntered_Id] END)
    )) OR ( EXISTS (SELECT 
        1 AS [C1]
        FROM   (SELECT 
            486524 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable13]
        UNION ALL
            SELECT 
            486525 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable14]
        UNION ALL
            SELECT 
            486526 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable15]
        UNION ALL
            SELECT 
            508376 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable16]
        UNION ALL
            SELECT 
            508377 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable17]
        UNION ALL
            SELECT 
            508378 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable18]) AS [UnionAll15]
        LEFT OUTER JOIN  (SELECT 
            [Extent5].[CleanedAddress_Id] AS [CleanedAddress_Id], 
            '2X0X' AS [C1]
            FROM [dbo].[TicketMetadata] AS [Extent5]
            WHERE ([Extent5].[Discriminator] = N'SIFTEscalationMetadata') AND ([Project1].[Metadata_Id] = [Extent5].[TicketMetadataID]) ) AS [Project24] ON 1 = 1
        WHERE [UnionAll15].[C1] = (CASE WHEN ([Project24].[C1] LIKE '2X0X%') THEN [Project24].[CleanedAddress_Id] END)
    ))) 

让 EF 在此处生成更好查询的最佳方法是什么?如果能做到就好了:

SELECT ...
WHERE Address_Id in(486524, 486525, 486526, 508376, 508377, 508378)
      OR AddressEntered_Id in(486524, 486525, 486526, 508376, 508377, 508378)
      OR CleanedAddress_Id in(486524, 486525, 486526, 508376, 508377, 508378)

正如@Cory 指出的那样,Contains 扩展方法在 SQL 中被转换为 IN,因此您应该使用它而不是 Any,它被转换为 EXIST:

return await _tickets.OfType<SIFTEscalationMetadata>()
                     .Where(t =>addesses.Contains(t.Address.Id) ||
                               addesses.Contains(t.AddressEntered.Id) ||
                               addesses.Contains(t.CleanedAddress.Id)).ToArrayAsync();

并且您还应该使用 OfType 扩展方法来仅获取 SIFTEscalationMetadata 个实体

根据您的代码示例,我假设如下。

_tickets 是 class 的项目序列,比方说票。序列中的每个项目都有一个 属性 元数据。 MetaData 的 returned 值可能是也可能不是 SIFTEscalationMetaData。 (这是真的吗?还是每个元数据都是 SIFTEscalationMetaData?)

当 属性 MetaData 的值为 SIFTEscalationMetaData 时,您确定 属性 Ticket.MetaData 至少具有其他三个非空属性:Address、AddressEntered 和 CleanedAddress。此外,您确定这三个属性不会 return NULL,并且它们三个都有一个 属性 Id。

此外,您还有一个序列 addesses,其中每个元素的类型与 属性 Id 相同。我不知道Id的类型,但我们假设它是IdType,可能是一个int或一个字符串之类的

显然,您不仅需要 _tickets 序列中 属性 元数据是 SIFTEscalationMetaData 的票证,而且至少需要 Ticket.MetaData.Address、Ticket.MetaData 的 Id 值之一.AddressEntered 或 Ticke.MetaData.CleanedAddress 在地址集合中。

IEnumerable<Ticket> result = _tickets
    // first remember the ticket and convert the MetaData
    // to either null or a SIFTEscalationMetadata
    .Select(t => new
    {
        Ticket = t,
        MetaData = t.Metadata as SIFTEscalationMetadata,
    })
    // now take only those tickets where the metadata is not null
    .Where(t => t.MetaData != null)
    // and select from the remaining tickets the Ids you want to check
    .Select(t => new
    {
        Ticket = t.Ticket,
        Ids = new IdType[]
        {
            t.MetaData.Address.Id,
            t.MetaDate.AddressEntered.Id,
            t.MetaData.CleanedAddress.Id,
        },
     })
     // now take only those items where the intersection of the Ids and 
     // addesses has any elements
     .Where(t => addess.Interset(t.Ids).Any())
     .Select(t => t.Ticket);

优化处于匿名类型的中间 Select。匿名类型将成为 SQL 变量。每张票仅转换一次 SIFTEscalationMetaData。检查地址中是否有任何 ID 仅执行一次,并且仅针对具有正确元数据的票证。