我在搜索字符串字段时遇到了EF创建可怕查询的问题。它以懒惰程序员的风格产生了一个查询,包含空检查,强制扫描整个索引。
考虑以下问题。
查询1
var p1 = "x";
var r1 = ctx.Set<E>().FirstOrDefault(
subject =>
p1.Equals(subject.StringField));
查询2
const string p2 = "x";
var r2 = ctx.Set<E>().FirstOrDefault(
subject =>
p2.Equals(subject.StringField));
查询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检查。
答案 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。
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();
}