18

My code below gives me a NullReferenceException and the stack trace tells me the problem is in the Count method, so I'm pretty sure at some point foo, bar or baz is null.

My code:

IQueryable<IGrouping<string, Referral>> queryable= ...;
var dict = queryable.ToDictionary(g => g.Key.ToString(),
                                  g => g.Count(r => r.foo.bar.baz.dummy == "Success"));

I'm wondering what's a concise way to handle null cases. I learn that in C# 6.0 I can just do foo?.bar?.baz?.dummy, however the project I'm working on isn't C# 6.0

Mike G
  • 4,022
  • 9
  • 41
  • 63
Arch1tect
  • 3,680
  • 9
  • 43
  • 64
  • You can have an expression body for `g.Count()` but the null conditional operator was brought in because the process of doing so was so verbose. So unfortunately I don't think you're going to escape it – Callum Linington Oct 13 '15 at 08:41
  • Which version are you using? Or are you looking for any pre-6.0 solution? – LJNielsenDk Oct 13 '15 at 08:41
  • This might be a bit ugly but `g.Count(r => r.foo == null ? false : r.foo.bar == null ? false : r.foo.bar.baz == null ? false : r.foo.bar.baz.dummy == "Success")` ? – Thomas Ayoub Oct 13 '15 at 08:43
  • 1
    You should find out **which one** is null and why. Don't just hide the problem. "just do...." this might hide a true bug. – usr Oct 13 '15 at 11:27
  • Also, it is suspicious that you are getting a NRE in a **query**. All of this should remote to the database as SQL. Is this a true query or an AsQueryable() fake query? Probably another bug. – usr Oct 13 '15 at 11:28
  • How about ensuring those values are not `null`? Most properties should never be `null`, even if the C# type system is too primitive to enforce that. – CodesInChaos Oct 13 '15 at 11:39
  • 1
    @usr AFAIK there is no `IQueryable.ToDictionary`, so the `IEnumerable` overload gets chosen. – CodesInChaos Oct 13 '15 at 11:41
  • @CodesInChaos ah I did not even look at the method being called :) Then, it's a huge SELECT N+5 problem. – usr Oct 13 '15 at 11:44
  • @usr Only if it uses lazy loading for those sub properties. Otherwise it's simply a collection scan. I use those pretty often for rarely executed queries. – CodesInChaos Oct 13 '15 at 11:47
  • @CodesInChaos I don't think you can place an Include with EF to load grouping groups and also not to load properties inside grouping group elements. But I'm not an EF power user. – usr Oct 13 '15 at 11:55
  • or you could not use a data structure that is nested that deep and instead adhere to Law of Demeter – Dan Oct 13 '15 at 15:31
  • @usr , I don't really understand what you mean because I just started working and I have little experience with C#... This `NullReferenceException` happen very rarely, only happen to records on two days out of a whole year and there are millions records a year. And this only happened to our production site not test environment, I'm not supposed to look at the record there... – Arch1tect Oct 13 '15 at 15:36

4 Answers4

30

A solution for <6.0 would be:

.Count(r => r.foo != null && 
            r.foo.bar != null && 
            r.foo.bar.baz != null && 
            r.foo.bar.baz.dummy == "Success")

Exactly for complex constructs like the one above the null propagation operator was introduced.

Furthermore you could also refactor the expression into a private method:

private Expression<Func<Referral, bool>> Filter(string value)
{
    return r => r.foo != null && 
                r.foo.bar != null && 
                r.foo.bar.baz != null && 
                r.foo.bar.baz.dummy == value;
}

and use it as follows:

g => g.Count(Filter("Success"))
Michael Mairegger
  • 5,984
  • 27
  • 39
9

You can use the following extension methods.

public static TResult With<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)
    where TResult : class
    where TInput : class
{
    return o == null ? null : evaluator(o);
}

public static TResult Return<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator, TResult failureValue)
    where TInput : class
{
    return o == null ? failureValue : evaluator(o);
}

Their combination gives you a nice, readable API for handling nulls:

return foo
    .With(o => o.bar)
    .With(o => o.baz)
    .Return(o => o.dummy, null);
Sergey Kolodiy
  • 5,521
  • 1
  • 33
  • 54
  • 4
    While this is a good idea in general, it does not go well together with `IQueryable`. LINQ would not understand this and thuis execute the query on the client rather than on the database. – Georg Oct 13 '15 at 09:02
  • @Georg With the LINQ providers that translate to SQL, you can typically just write `foo.bar.baz.dummy` and not get any `NullReferenceException`, so you wouldn't need this `With` helper anyway. –  Oct 13 '15 at 18:49
  • @hvd Yes, because the you can typically set on database level whether NUL values should be propageted and this is also the default behavior. However, it is better if do not need an understanding what data source you will get. – Georg Oct 15 '15 at 06:29
  • @Georg Agreed in theory, but I do not think that is practical. There are already many differences between the providers that make the same query execute differently depending on the provider. In my experience, only the simplest most straightforward filters and projections can be truly provider-agnostic. But if your experience is different, then good for you, and I can see why you would try to keep it that way. –  Oct 15 '15 at 06:34
2

The problem is that the ToDictionary method isn't actually done on the queryable - instead, you get the whole collection, and do the aggregation in your application, rather than on the DB server.

So instead of using ToDictionary directly, use Select first:

IQueryable<IGrouping<string, Referral>> queryable= ...;
var dict = queryable.Select(g => new { Key = g.Key.ToString(),
                              Count = g.Count(r => r.foo.bar.baz.dummy == "Success") })
                    .ToDictionary(i => i.Key, i => i.Count);

This will make sure the aggregation is done in the database (where you don't care about those nulls) and not in the C# code (where you get a NullReferenceException).

Of course, this assumes that the queryable you're using is a DB query (or, to be more precise, a queryable that supports aggregation and has ANSI SQL-like NULL semantics). If you have a different custom queryable, it's not going to help (unless you explicitly add those NULL semantics yourself).

Luaan
  • 57,516
  • 7
  • 84
  • 100
-1
// if null, use null 
if(objectvariable == null) 
{
 // throw exception 
}

// if not null
if(objectvariable != null) 
{
 // continue  
}