【问题标题】:Why is Linq Contains generating this SQL?为什么 Linq 包含生成此 SQL?
【发布时间】:2016-08-23 21:09:31
【问题描述】:

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

联系人

ID
CompanyId
Email
Domain

电子邮件域

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

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

【问题讨论】:

  • 有必要处理NULL 的情况,因为在SQL 中,NULL != NULL。所以这导致value1 = value2 OR (value1 IS NULL AND value2 IS NULL)
  • 您使用的是哪个版本的 EF?也许你可以使用this,另见this
  • @Maarten - 使用 EF6。但是我不确定您是否遇到了问题 - 我可以在 Sql 中将其编写为 CTE 或子查询,它将检索指定公司的联系域,没有电子邮件,然后将其内部加入到 EmailDomains 表中。我理解 null != null 但是我根本不想考虑空值

标签: c# entity-framework linq


【解决方案1】:

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

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

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

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

this.Configuration.UseDatabaseNullSemantics = true;

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

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

但请注意,如果您在查询中比较可为空的字段并且不包括明确的null 检查,则打开该选项可能会导致一些意外结果,因此请谨慎使用。

【讨论】:

    猜你喜欢
    • 2021-09-23
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    • 2011-03-21
    • 2019-09-20
    • 1970-01-01
    • 1970-01-01
    • 2019-11-08
    相关资源
    最近更新 更多