LINQ statement using one of two parameters - c#

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.

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 check if FirstOrDefault is null and use it

I'm writing a query that uses FirstOrDefault after an OrderBy query, which should check if it isn't null first then use some data in it. Is there a better way than writing it like this:
int count = db.Items.Count(i =>
i.Assignments.OrderByDescending(a =>
a.DateAssigned).FirstOrDefault() != null
&&
i.Assignments.OrderByDescending(a =>
a.DateAssigned).FirstOrDefault().DateReturned == null)
What this code does is there are items that has many assignments, I take the latest assignment by date, then check if it exist, then run a condition on a property (DateReturned). As you see, this query is long, and most of my queries seem to look like this where I check for null first then run a second query on it using their properties. Is there a better way of doing this?
Just call .Any(a => a.DateReturned == null) to check whether there are any items that meet the condition.
If you only want to check the latest assignment, add .Take(1) before the .Any().
My take:
int count =
itemsQuery.Select(i => i.Assignments.OrderByDescending(a => a.DateAssigned))
.Count(i => i.FirstOrDefault() != null &&
i.First().DateReturned == null);
You can put the result in a variable to avoid doing the same thing twice:
int count = itemsQuery.Count(i => {
var f = i.Assignments.OrderByDescending(a => a.DateAssigned).FirstOrDefault();
return f != null && f.DateReturned == 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.

Where Predicates in LINQ

How can I specify conditions in Where predicates in LINQ without getting null reference exceptions. For instance, if q is an IQueryable how can I do like:
Expression<Func<ProductEntity,bool>> predicate = p => !search.CategoryId.HasValue || (search.CategoryId.HasValue && search.CategoryId == p.CategoryId);
var q2 = q.Where(predicate);
Here search is an object that holds possible search conditions that may or may not be set like search.CategoryId might not be set but if it is I want to get the products that are set by that condition.
When I do this I get null reference exceptions.
You can use the null-coalescing operator ?? to replace a possible null value with a default value. The following sets tries to match the search.Category if it exists or simply creates an "always true" expression. This will be optimized by any good Linq query provider (e.g. LinqToSql).
Expression<Func<ProductEntity,bool>> predicate = p => (search.CategoryId ?? p.CategoryId) == p.CategoryId);
var q2 = q.Where(predicate);
Another possibility would be to dynamically compose a query predicate using PredicateBuilder. That's the way I do it for searches with a similar pattern as you use:
var predicate = PredicateBuilder.True<Order>();
if (search.OrderId))
{
predicate = predicate.And(a => SqlMethods.Like(a.OrderID, search.OderID);
}
// ...
var results = q.Where(predicate);
Let's dissect the line:
Expression<Func<ProductEntity,bool> predicate = p => !search.CategoryId.HasValue
|| (search.CategoryId.HasValue && search.CategoryId == p.CategoryId)
var q2 = q.Where(predicate);
So how many ways can we get null problems?
search (your "captured" variable) could be null
p could be null, meaning there is a null in the list
you've handled the case of search.CategoryId being null (Nullable<T>)
but maybe p.CategoryId (the category on a record in the list) is null (Nullable<T>) - however, I'm not sure that this would cause a NullReferenceException
q (the list / source) could be null
So: out of 5 options you've eliminated 1; look at the other 4? There is also the definite possibility that the issue is caused by something invisible not shown in the code; for example the get could be:
public int? CategoryId {
get {return innerObject.CategoryId;}
}
and innerObject could be null; if you eliminate the other 4 (pretty easy to do), look at at this one as a last resort.

Test for List<T> membership using a List<T>

Does anyone know if there is a way to test for list membership utilizing a list. For example I have a class named Membership which has a property Rebates which is of type List<Enums.RebateType>. I want to test using a lambda expression to see if that list contains any rebates that are of a specific type. My orginal lambda expression is as follows
return Membership.Rebates.Exists(rebate =>
rebate.RebateType == Enums.RebateType.A &&
rebate.RebateStatus == Enums.RebateStatus.Approved);
Instead of having to do the following:
return Membership.Rebates.Exists(rebate =>
(rebate.RebateType == Enums.RebateType.A &&
rebate.RebateStatus == Enums.RebateStatus.Approved) ||
(rebate.RebateType == Enums.RebateType.B &&
rebate.RebateStatus == Enums.RebateStatus.Approved));
I was wondering if something similar to the following mocked up SQL syntax could be done via one Lambda expression.
SELECT COUNT(*)
FROM Membership.Rebates
WHERE RebateType IN (ValidRebateTypes) AND Approved = true
ValidRebateTypes is curently a List<Enums.RebateType> that I am testing for i.e. ValidRebateTypes = (Enums.RebateType.A, Enums.RebateType.B).
The work around I currently have is as follows:
bool exists = false;
foreach (Enums.RebateType rebateType in ValidRebateTypes())
{
exists = Membership.Rebates.Exists(
rebate =>
rebate.RebateType == rebateType &&
rebate.RebateStatus == Enums.RebateStatus.Approved);
if (exists) { break; }
}
return exists;
Sounds like you want:
Membership.Rebates.Where(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved);
You can then use .Count() for the count:
Membership.Rebates.Where(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved)
.Count();
Or .Any() to determine the existence of any that satisfy that condition
Membership.Rebates.Any(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved);
In addition to Marc's suggestion, I would recomment making ValidRebateTypes a HashSet<Enums.RebateType>. Not only is this likely to be more efficient (although possibly not for a small set), it also reveals your intent (ie. that there only be one of each value of RebateType in it).

Categories

Resources