Сравните обнуляемые типы в Linq и Sql - PullRequest
41 голосов
/ 25 февраля 2009

У меня есть объект Category, который имеет поле Nullable 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), результат кажется нормальным.

Ответы [ 7 ]

53 голосов
/ 23 октября 2010

Другой способ:

Where object.Equals(c.ParentId, categoryId)

или

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
29 голосов
/ 25 февраля 2009

Первое, что нужно сделать, это включить ведение журнала, чтобы увидеть, что было сгенерировано 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);
    }
5 голосов
/ 14 июня 2012

Вам необходимо использовать оператор Equals:

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

равно для типов, допускающих значение NULL, возвращается true , если:

  • Свойство HasValue имеет значение false, а другой параметр имеет значение null. То есть два нулевых значения равны по определению.
  • Свойство HasValue имеет значение true, а значение, возвращаемое свойством Value, равно другому параметру.

и возвращает false , если:

  • Свойство HasValue для текущей структуры Nullable имеет значение true, а другой параметр имеет значение null.
  • Свойство HasValue для текущей структуры Nullable имеет значение false, а другой параметр не равен NULL.
  • Свойство HasValue для текущей структуры Nullable имеет значение true, а значение, возвращаемое свойством Value, не равно другому параметру.

Подробнее здесь Обнуляемый <.T>. Метод равных

5 голосов
/ 25 февраля 2009

Я предполагаю, что это связано с довольно распространенным атрибутом СУБД - просто то, что две вещи равны нулю, не означает, что они равны.

Чтобы уточнить, попробуйте выполнить эти два запроса:

SELECT * FROM TABLE WHERE field = NULL

SELECT * FROM TABLE WHERE field IS NULL

Причина конструкции "IS NULL" заключается в том, что в мире СУБД NULL! = NULL, поскольку значение NULL состоит в том, что значение не определено. Поскольку NULL означает неопределенное, вы не можете сказать, что два нулевых значения равны, так как по определению вы не знаете, что это такое.

Когда вы явно проверяете «field == NULL», LINQ, вероятно, преобразует его в «field IS NULL». Но когда вы используете переменную, я предполагаю, что LINQ не выполняет это преобразование автоматически.

Вот сообщение форума MSDN с дополнительной информацией об этой проблеме.

Похоже, хороший "чит" - это изменить лямбду, чтобы она выглядела так:

c => c.ParentId.Equals(categoryId)
1 голос
/ 16 сентября 2013

Или вы можете просто использовать это. Это также переведет к более хорошему запросу sql

Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId)
1 голос
/ 25 февраля 2009

Как насчет чего-нибудь попроще?

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;
}
0 голосов
/ 30 марта 2016

Linq to Entities поддерживает Null Coelescing (??), поэтому просто конвертируйте null на лету в значение по умолчанию.

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