How to simplify complicated conditional clauses when using LINQ to SQL - c#

Complicated statements with lots of AND/OR compenents are horrible to read and prone to errors - in a normal IF statement I might make use of a method call to simplify the is statement - for example:
if (((user == myUser || user == yourUser) && user != Admin)
&& Something > SomethingElse
&& (thresholdDate > item.itemDate || (item.itemDate == null && item.itemType == itemIsDated))
)
{
DoStuff();
}
I could refactor out the user and date parts to make things easier to read:
if (
UserValid(user)
&& Something > SomethingElse
&& DateIsValid(thresholdDate, item)
)
{
DoStuff();
}
What can I do in a LINQ query to simplifiy the nested IF?
For example if I have something along the lines of:
var someResults = DataManager.Things
.Where(item => (item.UserName == currentUser.UserName
|| item.ParentUsername == currentUser.UserName)
&& (item.ItemType == (int) ItemType.MyType
|| item.ItemType == (int) ItemType.YourType)
&& item.Result == null
&& (
(item.Status == null
&& (item.ItemDate < thresholdDate
|| item.ItemType == (int) ItemType.YourType)
)
||
(item.Status != null &&
item.Status != "Rejected")
)
)
** not actual code - just a simplified and generic example.
I'd like to be able to extract parts of the logic into methods - or in some other way seperate out the AND / OR mess such that it's clear what is going on.
I've tried adding a method to the 'item' to perfrom some of the logic a kind of IsValidType(typeOptions) method - this compiles fine but LINQ complains that it does't recognise the method at runtime.
I could use a property on the item - but then I can't pass any context information (which makes it of limited use)
How do you go about making this kind of query readable?

As Tim mentioned in a comment, you can use methods in most LINQ-to-something implementations.
The result would look like this:
.Where(item => CheckUserName(item)
&& CheckItemType(item)
&& CheckItemResult(item)
&& CheckItemStatus(item));
In case of LINQ to SQL, LINQ to Entities or other remote-executed implementations, you can at least take advantage of query optimization and rewrite all && to separate Where calls, because they are equivalent:
.Where(item => item.UserName == currentUser.UserName
|| item.ParentUsername == currentUser.UserName)
.Where(item => item.ItemType == (int) ItemType.MyType
|| item.ItemType == (int) ItemType.YourType)
.Where(item => item.Result == null)
.Where(item => (item.Status == null
&& (item.ItemDate < thresholdDate
|| item.ItemType == (int) ItemType.YourType))
|| (item.Status != null
&& item.Status != "Rejected"));
The resulting query will join the where clauses into one enumerator.

I assume you can't do it directly in your code. Of course if you had plenty of similar conditions, you could generate the expression for them dynamically. But you can do very little with single big Where condition here not hurting the performance =(.
If you consider it more appripriate, you could split your single big condition to few consequent Where() calls. LINQ tree when converting to SQL will merge conditions and generate single select. DBMS can also optimize the generated SQL for maximum efficiency. So you can in some cases intentionally write not optimized queries for better readability and rely on automatic optimizations. Though be careful with it and check if your specific conditions really gets merged and optimized as you expect.
If you can move this code to SQL stored procedure, you could configure LINQ to SQL to call this procedure when you need. That will simplify the calling code, but move complications to SQL. I suppose you can even extract some of your conditions to SQL functions and make them being called, using in the Where clause. Though to be honest I never used it - just read about possibility. For more details and example look at this article: http://weblogs.asp.net/zeeshanhirani/archive/2008/05/21/table-valued-functions-in-linq-to-sql.aspx

Three very different options:
Use Resharper. It contains a number of code inspections and refactorings to reduce if statements or point out redundant predicates.
In linq-to-sql you can skip most check for null. That is because the expressions are translated into SQL and in SQL there is no null reference exception. A linq (to sql) clause like where a.Name == "x" just yields false when there is no a. In linq-to-objects this would throw a null reference exception.
For comparisons that are done really often you may consider adding computed columns to your database tables. Like a column IsRejected that evaluates Status = 'Rejected'. In linq that would reduce to where !item.IsRejected.

Related

Entity Framework Core : how to add conditional where clause from input filter parameter?

I am not sure if this is possible, but I have a linq to sql statement that takes in input filter parameter which could be null or missing a value like so
public List<MyViewModel> GetRecords(SearchDto? filter)
{
List<MyViewModel> results =
this.dbContext.MyTable
Where(s => s.IsActive == 'Y' &&
filter != null && !stringIsNullOrEmpty(s.deptId) && s.deptId == filter.deptId)
ToList();
}
When I add the conditional filter in the Where clause, my result is 0 count when the filter is null or empty.
I prefer not to do the old fashion way which is after the linq is call, add many lines of code like the following
if (!string.IsNullEmpty(filter.deptId)
{
results = result.Where(s => s.deptId == filter.deptId);
}
I would like to add the conditional Where filter in the linq statement instead of old fashion C# code.
Thanks for any advice
To correctly represent conditionally added filtering like in the "old fashion way" you should use something like this:
.Where(s.IsActive == 'Y'
&& (filter == null
|| (string.IsNullOrEmpty(s.deptId) || s.deptId == filter.deptId))`
So the "optional" condition evaluates to true for your AND clause when filter is null or empty.

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".

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();
}

LINQ and C# - Dealing with a potentially null parameter

I am relatively new to LINQ but looking for some "best practice" advice on how to deal with the following. I know there are many ways to deal with this, but looking to see how more experienced people would write the code.
My LINQ at present:
var company = (from c in db.RPTINQUIRies
where c.CONCOM == concom && c.LOPER == engineer
orderby c.CREATION_DATE descending
select c);
Now the ActionResult parameter that is being passed in here (engineer) may or may not be empty. Where it is empty, I essentially want to remove the && C.LOPER == engineer clause all together.
What's the best way to deal with this?
It sounds like you just want:
where c.CONCOM == concom && (engineer == null || c.LOPER == engineer)
Alternatively, you could build up the query step by step:
var query = db.RPTINQUIRies.Where(c => c.CONCOM == concom);
if (engineer != null)
{
query = query.Where(c => c.LOPER == engineer);
}
query = query.OrderByDescending(c => c.CREATION_DATE);

Linq where column == (null reference) not the same as column == null

I came across a rather strange problem with linq-to-sql. In the following example,
var survey = (from s in dbContext.crmc_Surveys
where (s.crmc_Retail_Trade_Id == tradeId) && (s.State_.Equals(state))
select s).First();
If tradeId is null, it doesn't behave as if I had specified null specifically like this instead,
var survey = (from s in dbContext.crmc_Surveys
where (s.crmc_Retail_Trade_Id == null) && (s.State_.Equals(state))
select s).First();
Which is my desired behavior. In fact it doesn't return anything unless both values are non-null. I can't figure out how to accomplish this short of several different linq queries. Any ideas?
Change where (s.crmc_Retail_Trade_Id == tradeId)
to
where (s.crmc_Retail_Trade_Id == tradeId ||
(tradeId == null && s.crmc_Retail_Trade_Id == null))
Edit - based on this post by Brant Lamborn, it looks like the following would do what you want:
where (object.Equals(s.crmc_Retail_Trade_Id, tradeId))
The Null Semantics (LINQ to SQL) MSDN page links to some interesting info:
LINQ to SQL does not impose C# null or
Visual Basic nothing comparison
semantics on SQL. Comparison operators
are syntactically translated to their
SQL equivalents. The semantics reflect
SQL semantics as defined by server or
connection settings. Two null values
are considered unequal under default
SQL Server settings (although you can
change the settings to change the
semantics). Regardless, LINQ to SQL
does not consider server settings in
query translation.
A comparison with the literal null
(nothing) is translated to the
appropriate SQL version (is null or is
not null).
The value of null (nothing) in
collation is defined by SQL Server;
LINQ to SQL does not change the
collation.
Another option to solve this, as I ran across this problem as well.
where (tradeId == null ? s.crmc_Retail_Trade_Id == null : s.crmc_Retail_Trade_Id == tradeId)
Not sure on this one, but I suspect when linq-to-sql translates that to an sql query string you get a slightly different expression specifying null directly such that at some point you end up comparing NULL to itself, and NULL=NULL is defined to be false.
I am not familiar with Linq, however in general:
NULL represents a missing, unknown, or undefined value. Strictly speaking, a variable cannot equal NULL; low-lvel languages which provide this construct usually do so as a convenience because there is no easy alternative -- at a higher level it's usually better to rely on ISNULL, defined, or whatever features your language supplies.
One undefined variable is not equal to another undefined variable (and the same applies to NULL == NULL). Joe Celko has a good example of writing a query to find all people whose hair colour matches the colour of the vehicle they drive. Should this query match a bald man who walks everywhere?
It's better to make sp for this purpose because linq will perform iteration it takes while for your assistance if you are using linq.
var c = lstQ_Buffer.Where(q => (((semesterId == 0 || semesterId == null ? q.fkSemesterId == null : q.fkSemesterId == semesterId) && (subjectSpecialtyId == 0 || subjectSpecialtyId == null ? q.fkSubSpecialtyId == null : q.fkSubSpecialtyId == subSpecialtyId) && (subTopicId == 0 || subTopicId == null ? q.fkSubTopicId == null : q.fkSubTopicId == subTopicId)) && (q.fkProgramId == programId && q.fkYearId == yearId && q.fkCourse_ModuleId == courseModuleId && q.fkSubject_SpecialtyId == subjectSpecialtyId && q.fkTopicId == topicId && q.fkDifficultyLevelId == diffucultyLevelId))).ToList();

Categories

Resources