简体   繁体   中英

Why filtering this IEnumerable throws an NullReferenceException?

I have a peculiar problem I do not seem to solve. I am very sure this error comes from my lack of a deep understanding of how LINQ query work

  • The following service returns a list of products and works fine (and nothing is null)

    IEnumerable<Product> products = await _productRepository.GetProductList();

    I have tested the returned products and none of their properties (eg, Name, Price, Description) are null. In fact, I can display them on a table.

  • However, if I try to filter the returned products, I get an NullReferenceException

IEnumerable<Product> products =products.Where(x=>x.Name.ToLower().StartsWith(keyword.ToLower())).ToList();

The above code throws a NullReferenceException even though I have checked the property Name is not null (in fact, nothing is null)

How can this problem be solved?

This code would not compile:

IEnumerable<Product> products = await _productRepository.GetProductList();
IEnumerable<Product> products = products.Where(x=>x.Name.ToLower().StartsWith(keyword.ToLower())).ToList();

So you would need to be doing something a bit different to what your example states:

If you have something like:

IEnumerable<Product> products = await _productRepository.GetProductList()
    .Where(x=>x.Name.ToLower().StartsWith(keyword.ToLower()))
    .ToListAsync();

... then this should compile. A NullReferenceException from this statement would likely be from keyword being null.

As mentioned in the above comments, this is a very poor performing way to fetch back data. The issue is that if your repository returns IEnumerable it will be loading ALL products into memory, only to have your consuming code filter down to a few.

If you are employing unit testing, the repository pattern I would recommend is to have the repositories return IQueryable<TEntity> rather than IEnumerable<TEntity> so that your consuming code can apply things like filtering:

The consuming statement would be identical:

IEnumerable<Product> products = await _productRepository.GetProductList()
    .Where(x=>x.Name.ToLower().StartsWith(keyword.ToLower()))
    .ToListAsync();

... except this way the Where condition will be applied through to the SQL that EF generates leading to a far more efficient query. This requires ensuring that the DbContext instance is scoped to cover both the caller and the repository. (Ie provided through dependency injection and scoped to the life of the web request for example)

If you aren't using unit testing then the Repository pattern doesn't add anything and it would be more efficient to just utilize the DbContext directly to produce efficient queries.

Not sure how you want to resolve cases that are null but my guess in your case you can assume it doesn't start with the keyword. Null conditional Operator paired with null coalesce is your best bet. Honestly you should always anticipate null references and know how you want to handle them. I assume keyword is a constant but if it is not that should also be null checked

products = products
    .Where(x => x?.Name?.ToLower().StartsWith(keyword.ToLower()) ?? false)
    .ToList();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM