【问题标题】:Compare nullable types in Linq to Sql将 Linq 中的可为空类型与 Sql 进行比较
【发布时间】:2009-02-25 14:05:05
【问题描述】:

我有一个具有 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) 时,结果似乎正常。

【问题讨论】:

  • 我找到了方法...会更新...

标签: c# linq-to-sql nullable


【解决方案1】:

其他方式:

Where object.Equals(c.ParentId, categoryId)

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

【讨论】:

  • 这非常适合我。那么,我们应该在谓词中默认使用 Equals(x, y) 而不是“==”,还是有其他使用 Equals 的陷阱?
  • 这对我不起作用,抛出“可空列应该有值”异常。
【解决方案2】:

首先要做的是记录日志,看看生成了什么 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();
}

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

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);
    }

【讨论】:

  • 似乎没有更好的方法来处理这个问题。谢谢!
  • 我遇到了同样的问题,做了同样的解决方法,正要问是否有更好的方法来解决这个问题。看起来没有:(这种行为真的很反直觉。
  • 我会说文字和变量之间的不一致比直觉更糟糕。谢谢你证实了我的怀疑。 +1
  • 真的应该修好了,我才知道这个,现在我怀疑我在以前的项目中写的一些代码可能也会受到影响,有点糟糕......
  • 我怀疑这是因为这两种类型不一致——我认为一种是 Monad (ericlippert.com/2013/02/25/monads-part-two),另一种是“空文字”ericlippert.com/2013/07/25/what-is-the-type-of-the-null-literal ...但我承认这一点例子让我头晕目眩。
【解决方案3】:

我的猜测是,这是由于 DBMS 的一个相当普遍的属性 - 仅仅因为两个东西都是 null 并不意味着它们是相等的。

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

SELECT * FROM TABLE WHERE field = NULL

SELECT * FROM TABLE WHERE field IS NULL

“IS NULL”构造的原因是在 DBMS 世界中,NULL != NULL 因为 NULL 的含义是值未定义。由于 NULL 表示未定义,因此您不能说两个 null 值相等,因为根据定义,您不知道它们是什么。

当您明确检查“field == NULL”时,LINQ 可能会将其转换为“field IS NULL”。但是当你使用变量时,我猜 LINQ 不会自动进行这种转换。

这里是an MSDN forum post,提供有关此问题的更多信息。

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

c => c.ParentId.Equals(categoryId)

【讨论】:

【解决方案4】:

你需要使用运算符Equals:

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

Equals 可空类型返回 true 如果:

  • HasValue 属性为假,其他参数为空。也就是说,根据定义,两个空值相等。
  • HasValue 属性为真,Value 属性返回的值等于其他参数。

并在以下情况下返回 false

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

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

【讨论】:

  • 这是一个正确的答案
  • 我在 LinqPad 中对此进行了测试,但这似乎不起作用。如果您传入“null”文字,sql 生成的测试 Categories.ParentID IS NULL 就像您期望的那样。但是如果你传入一个变量,它会测试 Categories.ParentID = p0,如果 p0 为空,这将不起作用。 @ariel 的 object.Equals(Categories.ParentID, value) 方法效果很好。
  • 这不起作用。如果我将null 传递为categoryId,则SQL 包含奇怪的(0 = 1) 条件,这绝不是真的。
【解决方案5】:

像这样更简单的东西呢?

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;
}

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-20
        • 1970-01-01
        • 1970-01-01
        • 2010-12-30
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        相关资源
        最近更新 更多