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