44

I have a Category entity which has a Nullable ParentId field. When the method below is executing and the categoryId is null, the result seems null however there are categories which has null ParentId value.

What is the problem in here, what am I missing?

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

    return subCategories;
}

By the way, when I change the condition to (c.ParentId == null), result seems normal.

Ali Ersöz
  • 15,860
  • 11
  • 50
  • 64

7 Answers7

55

Other way:

Where object.Equals(c.ParentId, categoryId)

or

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
ariel
  • 15,620
  • 12
  • 61
  • 73
  • This works for me perfectly. So, should we default to using Equals(x, y) rather than "==" in our predicate or are there other gotchas with Equals? – AngieM Nov 17 '16 at 14:20
  • This doesn't work for me, throwing me "the nullable column should have a value" exception. – AgentFire May 20 '19 at 08:56
30

The first thing to do is to put on logging, to see what TSQL was generated; for example:

ctx.Log = Console.Out;

LINQ-to-SQL seems to treat nulls a little inconsistently (depending on literal vs value):

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

So all I can suggest is use the top form with nulls!

i.e.

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

Update - I got it working "properly" using a custom 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);
    }
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 3
    It seems there is no better way to handle this. Thanks! – Ali Ersöz Feb 25 '09 at 14:23
  • 2
    I ran into this exact same problem, made the same workaround and was about to ask if there was a better way to do it. Looks like there isn't :( This behaviour is really counter intuitive. – Andrew Barrett Mar 04 '09 at 11:41
  • 3
    I would say the inconsistency between literals and variables is worse that counter intuitive. Thanks for confirming my suspicion. +1 – Jodrell Jul 12 '11 at 11:02
  • 1
    It should really be fixed, I just find out about this and now I suspect some code I wrote in previous projects may be impacted as well, kinda sucks... – Guillaume86 Nov 17 '11 at 15:52
  • I suspect that it's because the two types are inconsistent -- I think one is a Monad (http://ericlippert.com/2013/02/25/monads-part-two/) and the other is a 'null literal' http://ericlippert.com/2013/07/25/what-is-the-type-of-the-null-literal/ ... but I confess this example makes my head spin. – Dan Esparza Dec 14 '13 at 04:00
5

My guess is that it's due to a rather common attribute of DBMS's - Just because two things are both null does not mean they are equal.

To elaborate a bit, try executing these two queries:

SELECT * FROM TABLE WHERE field = NULL

SELECT * FROM TABLE WHERE field IS NULL

The reason for the "IS NULL" construct is that in the DBMS world, NULL != NULL since the meaning of NULL is that the value is undefined. Since NULL means undefined, you can't say that two null values are equal, since by definition you don't know what they are.

When you explicitly check for "field == NULL", LINQ probably converts that to "field IS NULL". But when you use a variable, I'm guessing that LINQ doesn't automatically do that conversion.

Here's an MSDN forum post with more info about this issue.

Looks like a good "cheat" is to change your lambda to look like this:

c => c.ParentId.Equals(categoryId)
Eric Petroelje
  • 59,820
  • 9
  • 127
  • 177
4

You need to use operator Equals:

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

Equals fot nullable types returns true if:

  • The HasValue property is false, and the other parameter is null. That is, two null values are equal by definition.
  • The HasValue property is true, and the value returned by the Value property is equal to the other parameter.

and returns false if:

  • The HasValue property for the current Nullable structure is true, and the other parameter is null.
  • The HasValue property for the current Nullable structure is false, and the other parameter is not null.
  • The HasValue property for the current Nullable structure is true, and the value returned by the Value property is not equal to the other parameter.

More info here Nullable<.T>.Equals Method

algreat
  • 8,592
  • 5
  • 41
  • 54
  • This is a proper answer – tggm Sep 17 '13 at 10:49
  • 3
    I tested this in LinqPad and this doesn't seem to work. If you pass in the 'null' literal, the sql generated tests Categories.ParentID IS NULL like you'd expect. But if you pass in a variable, it tests Categories.ParentID = p0, which will not work if p0 is null. The object.Equals(Categories.ParentID, value) approach from @ariel worked great though. – Jared Phelps Jan 13 '16 at 21:41
  • This doesn't work. if I pass `null` as `categoryId`, than SQL contains weird `(0 = 1)` condition, which is never true. – AgentFire May 20 '19 at 08:58
2

Or you can simply use this. It will also translate to a nicer sql query

Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId)
Jiří Herník
  • 2,412
  • 1
  • 25
  • 26
1

What about something simpler like this?

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;
}
Ryan Versaw
  • 6,417
  • 3
  • 30
  • 31
0

Linq to Entities supports Null Coelescing (??) so just convert the null on the fly to a default value.

Where(c => c.ParentId == categoryId ?? 0)
Kevbo
  • 947
  • 9
  • 12