43

我有一个具有 Nullable ParentId 字段的 Category 实体。当执行下面的方法并且 categoryId 为 null 时,结果似乎为 null 但是有些类别的 ParentId 值为 null。

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

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

    return subCategories;
}

顺便说一句,当我将条件更改为 (c.ParentId == null) 时,结果似乎正常。

4

7 回答 7

54

另一种方式:

Where object.Equals(c.ParentId, categoryId)

或者

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
于 2010-10-23T19:42:52.950 回答
30

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

ctx.Log = Console.Out;

LINQ-to-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();
}

所以我只能建议使用带有空值的顶级表单!

IE

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);
    }
于 2009-02-25T14:08:47.463 回答
5

我的猜测是,这是由于 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”。但是当您使用变量时,我猜 LINQ 不会自动进行这种转换。

这是一个 MSDN 论坛帖子,其中包含有关此问题的更多信息。

看起来一个好的“作弊”是将您的 lambda 更改为如下所示:

c => c.ParentId.Equals(categoryId)
于 2009-02-25T14:08:57.090 回答
4

您需要使用运算符 Equals:

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

Equals fot 可空类型在以下情况下返回true

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

并在以下情况下返回false

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

更多信息在这里Nullable<.T>.Equals 方法

于 2012-06-14T13:42:41.667 回答
1

像这样更简单的东西呢?

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;
}
于 2009-02-25T15:04:07.457 回答
1

或者你可以简单地使用它。它还将转换为更好的 sql 查询

Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId)
于 2013-09-16T11:27:30.120 回答
0

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

Where(c => c.ParentId == categoryId ?? 0)
于 2016-03-30T20:22:41.053 回答