为什么Linq包含生成此SQL?

时间:2016-08-23 11:16:13

标签: c# entity-framework linq

背景:我正在开发一个系统来清理内部客户列表,并找出我们拥有该公司其他人的电子邮件地址的联系人的电子邮件地址。为了做到这一点,我有(简化)3个表:

联系人

ID
CompanyId
Email
Domain

EmailDomains

ID
Domain
EmailFormat
EmailFormatConfirmed

我有一个手动例行程序说,鉴于公司,找到我的下一个联系人,我们有他们的域名但不是他们的电子邮件地址:

int companyId = 53;

var emails = Contacts.Where(p => p.companyId == companyId
                                            && p.Email == null
                                            && !string.IsNullOrEmpty(p.Domain)).Select(p => p.Domain);
var domain =
    EmailDomains.FirstOrDefault(
        d => !d.EmailFormatConfirmed 
            && !string.IsNullOrEmpty(d.Domain)
            && emails.Contains(d.Domain));

此查询运行速度非常慢,并且检出生成的Sql:

    -- Region Parameters
DECLARE @p__linq__0 Int = 53
-- EndRegion
SELECT TOP (1) 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Domain] AS [Domain], 
    [Extent1].[EmailFormat] AS [EmailFormat], 
    [Extent1].[EmailFormatConfirmed] AS [EmailFormatConfirmed], 
    FROM [dbo].[EmailDomain] AS [Extent1]
    WHERE ([Extent1].[EmailFormatConfirmed] <> 1) 
        AND ( NOT (([Extent1].[Domain] IS NULL) OR ((LEN([Extent1].[Domain])) = 0))) 
        AND ( EXISTS (
            SELECT 1 AS [C1]
            FROM [dbo].[Contacts] AS [Extent2]
            WHERE ([Extent2].[CompanyId] = @p__linq__0) 
                AND ([Extent2].[Email] IS NULL) 
                    AND ( NOT (([Extent2].[Domain] IS NULL) OR ((LEN([Extent2].[Domain])) = 0))) 
                    AND (([Extent2].[Domain] = [Extent1].[Domain]) OR (([Extent2].[Domain] IS NULL) AND ([Extent1].[Domain] IS NULL)))
    ))

我可以看到,在exists子句的末尾,有问题的部分是OR (([Extent2].[Domain] IS NULL) AND ([Extent1].[Domain] IS NULL))。为什么会这样呢?我无法理解它是如何有效的,如果我手动编写Sql(目前我将不得不回退)它不会。我错过了一些明显的东西吗删除它会使查询运行得非常快(正如预期的那样 - 有很多空域在这里有效地交叉连接)

1 个答案:

答案 0 :(得分:0)

在EF6中,此行为由DbContextConfiguration UseDatabaseNullSemantics属性控制(默认情况下为 false ):

  

获取或设置一个值,该值指示在比较两个操作数时是否显示数据库空语义,这两个操作数都可能为空。默认值为false。例如(operand1 == operand2)将被翻译为:(operand1 = operand2)如果UseDatabaseNullSemantics分别为true(((operand1 = operand2)AND(NOT(operand1 IS NULL或operand2 IS NULL)))OR((operand1 IS) NULL)AND(operand2 IS NULL)))如果UseDatabaseNullSemantics为false。

所以只需将其转为true即可解决问题。

您可以在DbContext构造函数中执行此操作:

this.Configuration.UseDatabaseNullSemantics = true;

或仅在执行命令之前针对特定的上下文实例:

var db = new YourDbContext();
db.Configuration.UseDatabaseNullSemantics = true;
var emails = db.Contacts.Where(...)
...

但请注意,如果您比较查询中的可空字段并且不包含显式null检查,则启用此选项可能会导致一些意外结果,因此请谨慎使用。