linq where clause and count result in null exception - c#

The code below works unless p.School.SchoolName turns out to be null, in which case it results in a NullReferenceException.
if (ExistingUsers.Where(p => p.StudentID == item.StaffID &&
p.School.SchoolName == item.SchoolID).Count() > 0)
{
// Do stuff.
}
ExistingUsers is a list of users:
public List<User> ExistingUsers;
Here is the relevant portion of the stacktrace:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Linq.Enumerable.WhereListIterator1.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable1 source)
How should I handle this where clause?
Thanks very much in advance.

I suspect p.School is null, not SchoolName. Simply add a null check before accessing SchoolName. Also, use Any() to check if there are any results instead of Count() > 0 unless you're really in need of the count. This performs better since not all items are iterated if any exist.
var result = ExistingUsers.Where(p => p.StudentID == item.StaffID
&& p.School != null
&& p.School.SchoolName == item.SchoolID)
.Any();
if (result) { /* do something */ }

For all database nullable columns, we should either add null check or do simple comparision a == b instead of a.ToLower() == b.ToLower() or similar string operations.
My observation as below:
As they get iterated through Enumerable of LINQ Query for comparision against with input string/value, any null value (of database column) and operations on it would raise exception, but Enumerable becomes NULL, though query is not null.

In the case where you want to get the null value (all the student, with school or not) Use left join.
There are a good example on MSDN

If I remember correctly (not at my developer PC at the moment and can't check with Reflector), using the == operator results in calling the instance implementation string.Equals(string), not the static implementation String.Equals(string, string).
Assuming that your problem is due to SchoolName being null, as you suggest, try this:
if (ExistingUsers.Where(
p => p.StudentID == item.StaffID
&& String.Equals( p.School.SchoolName, item.SchoolID)).Count() > 0)
{
// Do stuff.
}
Of course, comments by other answers count as well:
Using Any() instead of Count() > 0 will generally perform better
If p.School is the null, you'll need an extra check
Hope this helps.

Related

How to skip conditions in "Where" clause with multiple conditions?

I"m working on a WinfForms/C# software for automotive key management that's has a SQL query thats searchs in the table like that:
DataAdapter = SetAdapter($"SELECT * FROM keylist WHERE MANUFACTOR LIKE #manufactor AND KEYTYPE LIKE #type AND SERVICETYPE LIKE #service AND USER_ID = #_user");
So in the begginig I used to use the query directly in the search function but as the project grew, it ended up leaving it without performance, because the query was called directly on the remote server. So I decided to move everything to a list in C # and do the search with lambdas functions.
This is the function with lambda expression:
public List<Key> SearchFilter(string manufactor, string type, string service)
{
return _keys.Where(key => key.Manufactor == manufactor
&& key.Type == type
&& key.ServiceType == service).ToList();
}
The problem is:
In the SQL syntax, when you leave one or more fields, it automatically ignores and checks for the other, but when I use Where <> in the LINQ for example, it ends up returning null or items that do not satisfy conditions and returning other objects.
When I leave one or two of the parameters null, it returns no value. By the way, if I use || instead of && it returns undesirable values.
Is there a way to check if the condition is null and skip to the next clause and return only the values that were passed?
if I understand correctly, I think I should make several "Where" conditions, one for each condition, that is:
return _keys.Where(key => manufactor != string.Empty ? key.Manufactor == manufactor ? key.Manufactor != string.Empty)
.Where(key => key.Type == type)
.Where(key => key.ServiceType == service)
.ToList();
You can also "exclude" or "include" filters if there is value to filter as I did with "key.Manufactor".

LINQ statement using one of two parameters

I'm working on a LINQ statement. I have a table of cities where the records have either a countryId or a stateId. I'd like to just write the one statement and have the where clause check to see which of the two parameters is null and then select on the one that is not.
Here's what I'm working with:
public List<City> Cities(int? countryTypeId, int? stateTypeId)
{
if (countryTypeId == null && stateTypeId == null)
return null;
return _db.City
.Where(x => x.StateTypeId == stateTypeId
&& x.CountryTypeId == countryTypeId)
.OrderBy(x => x.Description)
.ToDTOs();
}
I'm pretty new to LINQ, and I know this code isn't right, just adding it for context.
If the State and Country ids are all >0 you simply can do this, no need to check for null
.Where(x => x.StateTypeId == stateTypeId.GetValueOrDefault()
&& x.CountryTypeId == countryTypeId.GetValueOrDefault())
Else you need to add the condition if those nullable inputs have value or not, as mentioned in the comment
Edit: after seeing some comments, if you are looking for list of cities based on either of the parameters, then you should be using || not && in your where condition
Where(x => (stateTypeId.HasValue && stateTypeId.Value == x.StateTypeId)
|| (countryTypeId.HasValue && countryTypeId.Value == x.CountryTypeId))
Note the order matters, this code will first check if stateTypeId has value and if it has it'll match only the cities with that stateTypeId
_db.City.Where(c => c.CountryTypeId?.Equals(countryTypeId) ?? false
| c.StateTypeId?.Equals(stateTypeId) ?? false);
Using null conditional operators - when a type Id is null use the null coalescing operator to return false and fail the match - otherwise check for equality and return matching.
Note you cannot short circuit the OR operator here!
I'm not sure if this is the case, but if one of the input parameters was always null and the entries were guaranteed to always have one property null, the following would be a cool solution:
_db.City.Where(c => (c.CountryTypeId ?? c.StateTypeId) == (countryTypeId ?? stateTypeId))
My DBA has sufficiently beaten it into my head that ignoring parameters in a query (ex: WHERE Field = #PARAM or #PARAM IS NULL) can result in very bad things. As a result, I would encourage you to conditionally add only the parameters that you absolutely need. Fortunately, given that you are working with just two possible parameters, this is trivial.
Start with the base of your query, and then add to it.
var queryBase = _db.City.OrderBy(x => x.Description);
if (countryTypeId.HasValue)
{
queryBase = queryBase.Where(x => x.CountryTypeId == countryTypeId);
}
if (stateTypeId.HasValue)
{
queryBase = queryBase.Where(x => x.StateTypeId == stateTypeId);
}
return queryBase.ToDTOs(); // or .ToList() for a more universal outcome
Add whatever logic you may need if parameters are mutually exclusive, one supercedes the other, etc.

Null reference exception being thrown in EF LINQ query if-clause

I couldn't find the exact words to explain what's happening, so if this is a duplicated question, I apologize.
I tried to do a quite simple AND condition if-clause inside a LINQ Query, in order to check if an object is null and then verify if its property is equal or not the column I wanted to compare.
The code:
public IEnumerable<Plan> GetPlans(Plan plan)
{
return _context.Plans.Where(e =>
e.Situation == plan.Situation &&
e.Notes.Contains(plan.Notes) &&
(plan.Excercise != null && plan.Exercise.Year > 0 ? e.Exercise.Year == plan.Exercise.Year: true)).ToList();
}
I've already done this kind of check a dozen times before in .NET 4.5, without having any kind of issue.
But now, in the first .NET Core 2.0 project I'm working on, I had the following error:
An exception was thrown while attempting to evaluate a LINQ query parameter expression. To show additional information call EnableSensitiveDataLogging() when overriding DbContext.OnConfiguring.
The inner exception is clearer: NULL REFERENCE EXCEPTION.
After some tests, I found out that the error happens when plan.Exercise comes null, even if I try to avoid the exception by checking at first if it's null or not.
If I try to do the same check directly in Immediate Window, it returns "false", as it should be.
Am I missing something here? It could be an EF bug? Any particular reason why this works in .NET 4.5, for example, and not in .NET Core 2.0?
Thanks in advance.
UPDATE
Ivan's solution did the job:
Rewrite ? : constructs with equivalent ||
plan.Excercise == null || plan.Exercise.Year <= 0 || e.Excercise.Year == plan.Exercise.Year
It sounds like this might be a bug in EF Core (but I don't know this for sure).
One thing you might try is to fail fast if the base requirements of plan are not met, and more importantly, instead of using the ternary operator, use the traditional comparison operators along with parenthesis:
public IEnumerable<Plan> GetPlans(Plan plan)
{
if (plan == null) return new List<Plan>();
return _context.Plans
.Where(e =>
e.Situation == plan.Situation &&
e.Notes.Contains(plan.Notes) &&
(plan.Exercise == null ||
plan.Exercise.Year <= 0 ||
e.Excercise.Year == plan.Exercise.Year))
.ToList();
}
To avoid this issue, make sure that you are not evaluating on null object.
var exercice = await _repositoryExercice.FirstOrDefaultAsync(i => i.IsCurrent);
var depenses = _repositoryDepense.GetAll()
.Where( e => e.ExerciceId.Equals(exercice.Id))
.WhereIf(AbpSession.TenantId.HasValue, m => m.TenantId.Value.Equals(AbpSession.TenantId.Value))
.ToList();
The issue was causing by this line .Where( e => e.ExerciceId.Equals(exercice.Id)) because the variable exercice is null.
Best practice, I replaced that line by this :
...
.WhereIf(exercice != null, e => e.ExerciceId.Equals(exercice.Id))
...
how about simplifying your code into something like
public IEnumerable<Plan> GetPlans(int year)
{
return _context.Plans
.Where(e => e.Excercise.Year == year)
.ToList();
}

logical short-circuit and lambdas

I have the following lambda expression:
response = allDescendants
.Where(n =>
n.Caption.Contains(query) ||
n.Identifier.ToString().Contains(query) ||
n.Type.ToString().Contains(query) ||
n.Path.Contains(query) ||
n.Description.Contains(query) ||
(n.KeyWords != null && n.KeyWords.Any(kw => kw.Contains(query))) ||
n.SubType.Contains(query) ||
n.GroupingBy.Contains(query)
).ToList();
in the class definition the keyWord field is set to null:
private string[] keyWords = null;
public string[] KeyWords
{
get { return keyWords; }
set { keyWords = value; }
}
The line (n.KeyWords != null && n.KeyWords.Any(kw => kw.Contains(query))) throws a NullReferenceException because the the KeyWord field is null, but I was under the impression that since the check for null happened before the lambda, the entire expression should short-circuit to false. Is this something specific to do with lambdas or something else I'm not getting?
Edit:
I have found the culprit, it was a constructor that sets the string array to string[1] instead of null.
The debugger just marks that whole line. n.KeyWords was not null since, indeed, && short-circuits. (Or, that KeyWords property returns non-null the first time and null the second time it is called!)
Look at the call stack to see in what method the crash actually happened. The lambda will be on top and use can use the debugger to examine the value of kw which will be null.
As everyone in the comments suggested, I have looked for a code path that sets elements of my array to null instead of the entire array and have found it. It now sets the entire array to null and the code works as intended.

Null coalesce not working in LINQ query

Take this:
int? item1 = null;
int? item2 = null;
someObjectList.Where(x => x.SomeItem1 == (item1 ?? x.SomeItem1)
&& x.SomeItem2 == (item2 ?? x.SomeItem2)
);
Where someObjectList is not empty and SomeItem1 and SomeItem2 is null in all the objects in the list.
Why is it returning nothing?
EDIT:
My Code:
public void GetPlacementsByMaterial(long clientMaterialID)
{
ClientMaterial clientMaterial = ((ApplicationEntityModel)NavigationItem.ObjectContext).ClientMaterial.FirstOrDefault(x => x.ClientMaterialID == clientMaterialID);
var list = GetPlacementList(supplier, mediaSpace);
PlacementsList = list.Where(x => x.MediaCategoryFormatID == (clientMaterial.MediaCategoryFormatID ?? x.MediaCategoryFormatID)
&& x.MediaCategorySizeID == (clientMaterial.MediaCategorySizeID ?? x.MediaCategorySizeID)
);
}
All ID's are Nullable<long>.
EDIT:
SQL Profiler:
SELECT *
FROM [dbo].[CampaignSchedulePlacements] AS [Extent5]
WHERE ([Extent5].[MediaCategoryFormatID] = [Extent5].[MediaCategoryFormatID]) AND ([Extent5].[MediaCategorySizeID] = [Extent5].[MediaCategorySizeID])
Note: cleaned up the `SQL.
In SQL, NULL is not equal to NULL.
You can interpret NULL as meaning: "there is value, but I don't know what it is". So if you're comparing two NULL values, you're really asking "is the first unknown value equal to the second unknown value?" Of course, there is no reason to assume they are, so SQL will say "no".
I am assuming that this is causing your problem. You can verify that by looking at the actual SQL produced. If it's using the SQL = operator, this is indeed the problem. You can verify that by running the SQL in a database tool, such as SQL Management Studio in case you're using SQL Server.
UPDATE:
The condition
([Extent5].[MediaCategoryFormatID] = [Extent5].[MediaCategoryFormatID])
will indeed return false when [Extent5].[MediaCategoryFormatID] is NULL.
That answers the question "Why is it returning nothing?"
However, another question come to mind: why would the entity framework generate that SQL from this linq query?
I'm afraid that linq to entities is not exactly known for the quality of its SQL generation, and this case seems to confirm that. You might consider Linq to SQL. Even if that seems to be a dead-end track in the long run, the current implementation if a lot better than linq to entities.
In either case, have you tried something like
someObjectList.Where(x =>
!item1.hasValue ||
x.SomeItem1.HasValue && x.SomeItem1.Value == item1.Value)
Make sure to verify that under the profiler as well though, linq to entities might mess it up too.

Categories

Resources