为什么EF使用不必要的空值检查生成SQL查询?

时间:2016-07-18 09:50:06

标签: c# sql-server entity-framework

我在搜索字符串字段时遇到了EF创建可怕查询的问题。它以懒惰程序员的风格产生了一个查询,包含空检查,强制扫描整个索引。

考虑以下问题。

  1. 查询1

    var p1 = "x";
    var r1 = ctx.Set<E>().FirstOrDefault(
                            subject =>
                                p1.Equals(subject.StringField));
    
  2. 查询2

    const string p2 = "x";
    var r2 = ctx.Set<E>().FirstOrDefault(
                            subject =>
                                p2.Equals(subject.StringField));
    
  3. 查询1生成

    WHERE (('x' = [Extent2].[StringField]) OR (('x' IS NULL) AND ([Extent2].[StringField] IS NULL))) 
    

    并在4秒内执行

    查询2生成

    WHERE (N'x' = [Extent2].[StringField]) 
    

    并在2毫秒内执行

    有没有人知道任何工作? (参数不能是用户输入输入的const,但不能为空。)

    N.B在分析时,两个查询都是由EF用sp_executesql编写的;因为如果它们刚被执行,查询优化器会否定OR&#39; x&#39; IS NULL检查。

    for @Martin

3 个答案:

答案 0 :(得分:32)

设置UseDatabaseNullSemantics = true;

  

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

https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbcontextconfiguration.usedatabasenullsemantics(v=vs.113).aspx

public class MyContext : DbContext
{
    public MyContext()
    {
        this.Configuration.UseDatabaseNullSemantics = true;
    }
}

你也可以从外部设置这个设置到你的dbContext实例,就像下面的代码示例一样,从我的角度来看(参见@GertArnold评论),这个apporach会更好,因为它不会改变默认的数据库行为或配置):

myDbContext.Configuration.UseDatabaseNullSemantics = true;

答案 1 :(得分:8)

您可以通过在StringField属性

上添加[Required]来解决此问题
public class Test
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Bar{ get; set; }
    public string Foo { get; set; }

}


 string p1 = "x";
 var query1 = new Context().Tests.Where(F => p1.Equals(F.Bar));

 var query2 = new Context().Tests.Where(F => p1.Equals(F.Foo));

这是query1

  

{SELECT       [Extent1]。[Id] AS [Id],       [Extent1]。[Bar] AS [Bar],       [Extent1]。[Foo] AS [Foo]       FROM [dbo]。[测试] AS [Extent1]       WHERE @ p__linq__0 = [Extent1]。[Bar]}

这是query2

  

{SELECT       [Extent1]。[Id] AS [Id],       [Extent1]。[Bar] AS [Bar],       [Extent1]。[Foo] AS [Foo]       FROM [dbo]。[测试] AS [Extent1]       WHERE(@ p__linq__0 = [Extent1]。[Foo])OR((@ p__linq__0 IS NULL)AND([Extent1]。[Bar2] IS NULL))}

答案 2 :(得分:3)

我的一位同事刚刚找到了一个非常好的解决方案。因为我已经发现使用常量会产生正确的SQL。我们想知道是否可以用常量替换表达式中的变量;事实证明你可以。我相信这种方法比在DB上下文中更改空设置更具侵入性。

public class Foo_test : EntityContextIntegrationSpec
        {

            private static string _foo = null;

            private static DataConnection _result;

            private Because _of = () => _result = EntityContext.Set<E>().Where(StringMatch<E>(x => x.StringField));

            private static Expression<Func<TSource, bool>> StringMatch<TSource>(Expression<Func<TSource, string>> prop)
            {
                var body = Expression.Equal(prop.Body, Expression.Constant(_foo));
                return Expression.Lambda<Func<TSource,bool>>(body, prop.Parameters[0]);                
            }

            [Test] public void Test() => _result.ShouldNotBeNull();
        }