Entity Framework Core where clause extra condiition - c#

I have this simple query, the problem is that the End property is DateTime? type and I would like to include the records where this value is null as well but I cannot seem to do it. I tried using the Terniary operator and still no results
await _context.Registos
.Where(r => r.Start.Date >= DateStart.Date && r.End.HasValue ? r.End.Value <= DateEnd.Date : !r.End.HasValue )
.AsNoTracking()
.ToListAsync();

A cleaner way, something like this:
await _context.Registos
.Where(r => r.Start.Date >= DateStart.Date
&& (r.End == null || r.End.Value <= DateEnd.Date))
.AsNoTracking()
.ToListAsync();

await _context.Registos
.Where(r => r.Start.Date >= DateStart.Date &&
(!r.End.HasValue || (r.End.Value <= DateEnd.Date)) )
.AsNoTracking()
.ToListAsync();

The only way of having a variable which hasn't been assigned a value in C# is for it to be a local variable - in which case at compile-time you can tell that it isn't definitely assigned by trying to read from it :)
I suspect you really want Nullable<DateTime> (or DateTime? with the C# syntactic sugar) - make it null to start with and then assign a normal DateTime value (which will be converted appropriately). Then you can just compare with null (or use the HasValue property) to see whether a "real" value has been set.

Related

Why LINQ can't pick up the null value C#

I try to use LINQ to filter the data, the data is from 3rd party API (JIRA Server), and ResolutionDateis DateTime type, I guess it use Nullable, anyway, I am pretty sure the value is null, but when I use LINQ, it just not work at all. The LINQ just can't do i.ResolutionDate == null, it always said there is no item match this condition. I'm pretty sure I have the issues their ResolutionDate is null.
https://developer.atlassian.com/server/jira/platform/database-issue-fields/
var foo = datas.Where(i =>
i.Created > date && i.Created <= date.AddDays(7) &&
i.ResolutionDate> date.AddDays(7) && i.ResolutionDate== null);
You could probably coalesce those into (i.ResolutionDate?.Date > date.AddDays(7))
Ultimately, it should have some condition that will return true when you do the comparison.
You should check if the value of ResolutionDate is null OR more than seven days in the future
var foo = datas.Where(i =>
i.Created > date && i.Created <= date.AddDays(7) &&
(i.ResolutionDate == null || i.ResolutionDate > date.AddDays(7)));

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

Check nullable type with LINQ

I have a method which takes nullable DateTime type effectiveDate as a parameter.
List<Scales> GetScales(scales scaleId, DateTime? effectiveDate, int count)
{
return scales.Where(rs => rs.ScaleID != scaleId)
.Where(rs => rs.Review.ReleaseDate >= effectiveDate)
.OrderByDescending(rs => rs.Review.ReleaseDate)
.Take(count)
.ToList();
}
This method returns a list. What I am trying to do is, if effectiveDate is null then it will not check .Where(rs => rs.Review.ReleaseDate >= effectiveDate) condition.
I can easily achieve this by a IF..ELSE condition. But I wanted to know, is it possible in a single LINQ statement ?
If you want to include all entries without effective date:
.Where(rs => !effectiveDate.HasValue || rs.Review.ReleaseDate >= effectiveDate)
You can use the conditional operator to do if-else checks.
But in your case, just use the fact that conditions that are chained with && or || will be evaluated from left to right, and evaluation of the condition is stopped after a truth value can be assigned. For the OR operator || this means that the condition is met either if the first criteria is true or the first is false and the second is true:
.Where(rs => !effectiveDate.HasValue || rs.Review.ReleaseDate >= effectiveDate)
What if you make it a compound condition saying
.Where(rs => effectiveDate.HasValue && rs.Review.ReleaseDate >= effectiveDate)
I just check this code and its works for me.
.Where(rs =>effectiveDate.HasValue? rs.Review.ReleaseDat>=effectiveDate.Value:true)

LINQ OrderBy different field type depending on IF statement

I'm trying to sort some data that can be in one of the following (inferred) states (in this order):
live (valid StartDate, null EndDate);
draft (null StartDate);
ended (valid EndDate).
I've inherited the following syntax on a IQueryable:
iQueryableData
.OrderBy(t => t.StartDate == null ? 1 : (t.EndDate == null ? 0 : 2))
.ThenByDescending(t => t.StartDate)
.ThenBy(t => t.PartnerId)
And that is fine, since it sorts on one of the first 3 columns of the table, depending on some IF statements.
Now I need to rewrite that to work in memory (so just LINQ, no IQueryable), on a different (but similar) model. Here's what the above query will roughly translate to:
data
.OrderBy(t => t.StartDate == null
? t.EndDate // DateTime
: (t.EndDate == null
? t.Id // integer
: t.Name // string
)
)
This obviously fails to compile, because
CS0173 C# Type of conditional expression cannot be determined because
there is no implicit conversion between 'int' and 'string'
Presumably, I could keep sorting by an integer, but I've no idea what the numbers would refer to in this case (the order of the property as written inside the class? The sorted by name order of the same thing?).
Unfortunately, all questions I've found related to mine are ordering based on an IF statement that's relying on an external value (not inside the data).
Use the ThenBy extension. This ensures that the previous order is maintained while applying a new order criteria. For each specific case return the desired property to participate in order (Name, Id, EndDate) so each group in the set will be sorted by these values. Use some constant value for the other items that do not meet the original criteria so their order remains unchanged by the current ThenBy.
items
//sort by live, draft and ended
.OrderBy(t => t.StartDate == null ? 1 : (t.EndDate == null ? 0 : 2))
//keep the live, draft and ended sort,
//and for the live items apply only a sort by ID,
//but let the other items in the previous order (live, draft and ended)
//by returning the same value for each (zero or null)
.ThenBy( t=> t.StartDate != null && t.EndDate == null ? t.ID : 0)
//the same
//keep the sort by live, draft, ended and now by ID for live ones only
//but for the draft items sort them by EndDate
//leave the others unaffected by this sort
.ThenBy( t=> t.StartDate == null && t.EndDate != null ? t.EndDate : default(DateTime?))
//same - sort ended items by name
.ThenBy( t=> t.StartDate != null && t.EndDate != null ? t.Name : null)
I suggest you to implement a comparer for your data. The compareTo method will handle some complex cases you may have : what if linq have to compare Date, Number or String ?

MVC linq to sql sum

Trying to get the values returned from a database based on the sum of a field.
But getting this message:
The cast to value type 'System.Decimal' failed because the
materialized value is null. Either the result type's generic parameter
or the query must use a nullable type.
It is valid for the database to contain no records for that user for that day, hence I went down the nullable route. In the good old days I would have built a Stored Procedure with `ISNULL` in it!!!
This is the basic expression I have:
decimal? foodCount = dbContext.fad_userFoods.Where(uf => uf.dateAdded == thisDate && uf.userID == thisGuid).Sum(uf=>(decimal?)uf.quantityAmount ?? 0m);
Googling it came up with the nullable definitions and use of the ?? with the "m" as it's decimal. But still the error persists!
Your collective help will be invaluable as ever. Thanks in advance.
Use the DefaultIfEmpty method. This will fill in a 0 if no value at all can be found.
decimal foodCount = dbContext.fad_userFoods
.Where(uf => uf.dateAdded == thisDate && uf.userID == thisGuid)
.Select(uf => uf.quantityAmount)
.DefaultIfEmpty()
.Sum();
Since it's a sum and not average you don't really mind null-values?
Why not simply removing the null-values?
decimal? foodCount = dbContext.fad_userFoods
.Where(uf =>
uf.dateAdded == thisDate &&
uf.userID == thisGuid &&
uf.quantityAmount != null)
.Sum(uf=> uf.quantityAmount);
Use Convert.ToDecimal(), this will handle your null issue.
decimal foodCount = dbContext.fad_userFoods.Where(uf => uf.dateAdded == thisDate
&& uf.userID == thisGuid)
.Sum(uf=> Convert.ToDecimal(uf.quantityAmount ?? 0m));
LINQ to Entities does not recognize the method 'System.Decimal
ToDecimal(System.Decimal)' method, and this method cannot be
translated into a store expression.
Edit:
decimal foodCount = dbContext.fad_userFoods.Where(uf => uf.dateAdded == thisDate
&& uf.userID == thisGuid)
.Sum(uf=> { decimal result;
decimal.TryParse(uf.quantityAmount,out result);
return result;});
The confusion originates from the fact the Sum in LINQ To Entities is processed a bit different than in LINQ To Objects. Although from declaration it looks like that calling it on a let say decimal will return 0, when the target set is empty actually the SQL SUM function returns NULL, even if the target column is not nullable.
Once you know that, there are two ways of resolving it.
Let say we have a table with decimal column and the original expression is
table.Sum(item => item.Column)
First way is to convert it using the pattern contained in the Maarten answer:
table.Select(item => item.Column).DefaultIfEmpty().Sum()
The second way is to explicitly convert the non nullable type to nullable inside the function and then apply null-coalescing operator to the result:
table.Sum(item => (decimal?)item.Column) ?? 0
Both ways work and produce one and the same result, so use the one that better suits your personal preferences.
For completeness, applying the second approach in your case would be to basically move the ?? 0 outside of the Sum call:
decimal foodCount = dbContext.fad_userFoods
.Where(uf => uf.dateAdded == thisDate && uf.userID == thisGuid)
.Sum(uf => (decimal?)uf.quantityAmount) ?? 0;

Categories

Resources