logical short-circuit and lambdas - c#

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.

Related

Filter a list of class with element which starts with specific letters

I have a list of a class object, I need to filter that list with element which starts with these letters "GHB" and then set a listview control dataconext to it to display the elements
if(myList.ToList().FindIndex(x=> x.Name !=null)!=-1 )
{
listview1.DataContext = myList.ToList().where(x=> x.Name.StarstWith("GHB"))
}
But it gives me an error when an element is null
It gives you the error because your if condition is actually useless. You check whether Name in at least one element is not null, if so you try to access the variable. This of course will fail, because you need only 1 element with a valid name and the rest still can have null values which will lead to the NullReferenceException
What you can do is: check in the where clause additionaly whether Name is not null and if so only then check whether it StartsWith("GHB"):
listview1.DataContext = myList.Where(x => x?.Name != null && x.Name.StartsWith("GHB")).ToList();
this way you can save yourself the if condition.
I guess what you where trying to check is if Name in all elements is not null. In this case you can use the All method:
if (myList.All(x=>x.Name != null)
EDIT: using the ? will avoid that Name is checked if an element in the List is entirely null:
myList.Where(x => x?.Name != null && x.Name.StartsWith("GHB")).ToList();
Try this:
listview1.DataContext = myList
.Where(x => x != null
&& !string.IsNullOrEmpty(x.Name)
&& x.Name.StarstWith("GHB"))
.ToList();
...and remove the if statement.

Where function on list with multiple conditions returns errors

I'm trying to filter a list based on a few criteria and the .Where() function gives me an error in 2 parts of the same method.
if (string.IsNullOrWhiteSpace(champs))
{
data = dal.GetVueTache().Where(t =>
t.ProjetDescription64.ToLower().Contains(filtre.ToLower())
// *This Line || t.ProjetDescription256.ToLower().Contains(filtre.ToLower())
|| t.Description256.ToLower().Contains(filtre.ToLower())
||t.ResponsableNomCourt.ToLower().Contains(filtre.ToLower())
|| t.PrioriteDesc.ToLower().Contains(filtre.ToLower())
).ToList();
}
If I use any of the previous conditions except the one on the nullable field alone I get a perfectly filtered list on that criteria, if I add an OR "||" then I get a System.NullReferenceException on the first criteria.
I also have a similar issue in another part of the same method
else
{
data = dal.GetVueTache().Where(t =>
t.GetType().GetProperty(champs).GetValue(t).ToString().ToLower().Contains(filtre.ToLower())
).ToList();
}
This one filters my list based on the criteria "filtre" on the property "champs". It works on every property but the second one, which is a nullable one. I understand that this is where the issue comes from, but I can't find a way to test if the property is null before evaluating it and work around this from inside the .Where() Method.
Any advice will be greatly appreciated!!
Edit :
Thanks to Ivan Stoev for his solution!
The correct syntax for testing the null value in the first case is:
|| (t.ProjetDescription256 != null && t.ProjetDescription256.ToLower().Contains(filtre.ToLower()))
In the second case:
(t.GetType().GetProperty(champs).GetValue(t) != null && t.GetType().GetProperty(champs).GetValue(t).ToString().ToLower().Contains(filtre.ToLower()))
Just do a null check either the old way:
|| (t.ProjetDescription256 != null && t.ProjetDescription256.ToLower().Contains(filtre.ToLower()))
or the C# 6 way (utilizing the null conditional operator):
|| t.ProjetDescription256?.ToLower().Contains(filtre.ToLower()) == true
Btw, you can greatly simplify similar checks and avoid such errors by writing a simple custom extension methods like this:
public static class StringExtensions
{
public static bool ContainsIgnoreCase(this string source, string target)
{
return source == null ? target == null : target != null && source.IndexOf(target, StringComparison.CurrentCultureIgnoreCase) >= 0;
}
}
so your snippet becomes simply:
data = dal.GetVueTache().Where(
t => t.ProjetDescription64.ContainsIgnoreCase(filtre)
|| t.ProjetDescription256.ContainsIgnoreCase(filtre)
|| t.Description256.ContainsIgnoreCase(filtre)
|| t.ResponsableNomCourt.ContainsIgnoreCase(filtre)
|| t.PrioriteDesc.ContainsIgnoreCase(filtre)
).ToList();

LINQ to Entities: All method not yielding the expected result

I have a pretty simple helper method to generate a unique code. To ensure that codes are unique I execute a LINQ to Entities query to verify that it isn't already in use.
My first attempt at writing this method worked perfectly:
public string GenerateUniqueSignUpCode()
{
while( true )
{
var code = Path.GetRandomFileName().Substring( 0, 6 ).ToUpper();
if( !Context.Users.Any(e => e.SignUpCode.ToUpper() == code) )
return code;
}
}
However, R# suggested that the LINQ expression could be simplified, which resulted in this method:
public string GenerateUniqueSignUpCode()
{
while( true )
{
var code = Path.GetRandomFileName().Substring( 0, 6 ).ToUpper();
if( Context.Users.All(e => e.SignUpCode.ToUpper() != code) )
return code;
}
}
This rewrite causes an infinite loop. The database does not contain any 6-character codes when the code is run so it should exit the loop on the first attempt (as does the first method shown).
Is All broken in EF 4.3.1 or what's going on?
My guess is that this will happen if SignupCode is null for any entry. The comparison using != won't give a "true" result, so All will return false.
Just a guess, but it's the kind of thing I've seen before. You could try:
if (Context.Users.All(e => e.SignUpCode == null ||
e.SignUpCode.ToUpper() != code))
Context.Users.All(e => e.SignUpCode.ToUpper() != code
should throw null reference exception if SignUpCode is null.
I guess expression is all fine. Data behind should have the problem

c# .net 4.0 : nullable input string parameter and the lambda expressions

I have a following method:
public IQueryable<Profile> FindAllProfiles(string CountryFrom, string CountryLoc)
{
return db.Profiles.Where(p => p.CountryFrom.CountryName.Equals(CountryFrom,StringComparison.OrdinalIgnoreCase));
}
Right now, it just filters by CountryFrom but I need to filter it by CountryLoc as well.
So how do I modify the Where filter?
Also, CountryFrom could be null or CountryLoc could be null. So how do I modify the method's signature to all the nullable input string parameter.
I know how to do this in SQL but I am not sure about lambda expressions or LINQ.
Thanks
Your question isn't actually LINQ-specific. You can put any boolean conditions inside the lambda that you want, such as:
p => p.CountryFrom.CountryName.Equals(...) && p.CountryLoc.CountryName.Equals(...)
To handle null values you can use the static string.Equals method instead, like this:
p => string.Equals(p.CountryFrom.CountryName, CountryName, StringComparison.OrdinalIgnoreCase)
If this starts getting too long you could always extract it into a method.
return db.Profiles
.Where(p => p.CountryFrom.CountryName.Equals(CountryFrom,
StringComparison.Ordinal.IgnoreCase) &&
p.CountryLoc.CountryName.Equals(CountryLoc,
StringComparison.Ordinal.IgnoreCase));
Sorry...there was no way to make that statement pretty.
Just remember you can use any boolean logic you'd like inside of the Where extension method. It's just as easy as it would be anywhere else.
Also, a string parameter can be null no matter what (it's a reference type) so you don't need to worry about making things nullable.
You'll want to use the logical and operator "&&" in your lambda expression to filter by both CountryFrom and CountryLoc. Ex
db.Profiles.Where(p => p.CountryFrom == CountryFrom && p.CountryLoc == CountryLoc);
In C# strings are reference types and therefore can be null. You can check for null values using
myString == null

linq where clause and count result in null exception

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.

Categories

Resources