将Linq中的可空types与Sql进行比较

我有一个具有可为空的ParentId字段的类别实体。 当下面的方法正在执行并且categoryId为空时,结果似乎为空,但是存在具有空ParentId值的类别。

这里有什么问题,我错过了什么?

public IEnumerable<ICategory> GetSubCategories(long? categoryId) { var subCategories = this.Repository.Categories.Where(c => c.ParentId == categoryId) .ToList().Cast<ICategory>(); return subCategories; } 

顺便说一下,当我改变条件(c.ParentId == null),结果似乎正常。

首先要做的就是logging日志,看看生成了什么TSQL; 例如:

 ctx.Log = Console.Out; 

LINQ到SQL看起来有点不一致(取决于字面值与值):

 using(var ctx = new DataClasses2DataContext()) { ctx.Log = Console.Out; int? mgr = (int?)null; // redundant int? for comparison... // 23 rows: var bosses1 = ctx.Employees.Where(x => x.ReportsTo == (int?)null).ToList(); // 0 rows: var bosses2 = ctx.Employees.Where(x => x.ReportsTo == mgr).ToList(); } 

所以我可以build议的是用空值的顶部forms!

 Expression<Func<Category,bool>> predicate; if(categoryId == null) { predicate = c=>c.ParentId == null; } else { predicate = c=>c.ParentId == categoryId; } var subCategories = this.Repository.Categories .Where(predicate).ToList().Cast<ICategory>(); 

更新 – 我使用自定义Expression “正常”工作:

  static void Main() { ShowEmps(29); // 4 rows ShowEmps(null); // 23 rows } static void ShowEmps(int? manager) { using (var ctx = new DataClasses2DataContext()) { ctx.Log = Console.Out; var emps = ctx.Employees.Where(x => x.ReportsTo, manager).ToList(); Console.WriteLine(emps.Count); } } static IQueryable<T> Where<T, TValue>( this IQueryable<T> source, Expression<Func<T, TValue?>> selector, TValue? value) where TValue : struct { var param = Expression.Parameter(typeof (T), "x"); var member = Expression.Invoke(selector, param); var body = Expression.Equal( member, Expression.Constant(value, typeof (TValue?))); var lambda = Expression.Lambda<Func<T,bool>>(body, param); return source.Where(lambda); } 

另一种方式:

 Where object.Equals(c.ParentId, categoryId) 

要么

 Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId) 

你需要使用运算符Equals:

  var subCategories = this.Repository.Categories.Where(c => c.ParentId.Equals(categoryId)) .ToList().Cast<ICategory>(); 

如果满足以下条件,则等于可空types返回true

  • HasValue属性为false,另一个参数为null。 也就是说,根据定义,两个空值是相等的。
  • HasValue属性为true,Value属性返回的值等于另一个参数。

并在以下情况返回false

  • 当前Nullable结构的HasValue属性为true,另一个参数为null。
  • 当前Nullable结构的HasValue属性为false,另一个参数不为null。
  • 当前Nullable结构的HasValue属性为true,并且由Value属性返回的值不等于另一个参数。

更多信息在这里可为空<.T> .Equals方法

我的猜测是,这是由于DBMS的一个相当常见的属性 – 只是因为两件事都是空的并不意味着它们是平等的。

详细一点,尝试执行这两个查询:

 SELECT * FROM TABLE WHERE field = NULL SELECT * FROM TABLE WHERE field IS NULL 

“IS NULL”结构的原因是在DBMS世界中,NULL!= NULL,因为NULL的含义是该值未定义。 由于NULL意味着未定义,所以不能说两个空值是相等的,因为根据定义你不知道它们是什么。

当你明确地检查“field == NULL”时,LINQ可能将其转换为“field IS NULL”。 但是,当你使用一个variables,我猜测LINQ不会自动执行该转换。

这是一个MSDN论坛post ,更多关于这个问题的信息。

看起来好像“骗子”就是把你的lambda换成这样的样子:

 c => c.ParentId.Equals(categoryId) 

那么简单的东西呢?

 public IEnumerable<ICategory> GetSubCategories(long? categoryId) { var subCategories = this.Repository.Categories.Where(c => (!categoryId.HasValue && c.ParentId == null) || c.ParentId == categoryId) .ToList().Cast<ICategory>(); return subCategories; } 

或者你可以简单地使用这个。 它也将转化为一个更好的SQL查询

 Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId) 

Linq to Entities支持Null Coelescing(??),所以只需将null即时转换为默认值即可。

 Where(c => c.ParentId == categoryId ?? 0)