Comparing dates in LINQ Entity Framework - c#

I'm trying to run the following query:
List<EPCRA_Events> x =
_dbContext.EPCRA_Events
.Where(e => e.DueDt.HasValue &&
(e.DueDt.Value - DateTime.Now).Days < 30)
.ToList();
But I keep getting an error
The LINQ expression ...xxx... could not be translated
After reviewing other posts I've seen a common solution is using DbFunctions class. However, after using Nuget to import Entity Framework 6.4.4 and Microsoft.EntityFrameworkCore 5.0.9, I don't have access to that class. Furthermore, the Entity class isn't in the System.Data namespace.
Any info on how I can get this query to run, or what I'm doing wrong using DbFunctions would be appreciated. Thanks.

Even if the query could be translated, it would be a bad query because it would have to calculate the difference for every single row before filtering. Indexing wouldn't help, because indexes are built from the stored values.
The solution is to calculate the cutoff date in advance and compare with the field directly.
This code will find records in the past 30 days
var cutoff=DateTime.Now.AddDays(-30);
List<EPCRA_Events> x = _dbContext
.Where(e => e.DueDt > cutoff)
.ToList();
While this will find records up to 30 days in the future :
var cutoff=DateTime.Now.AddDays(30);
List<EPCRA_Events> x = _dbContext
.Where(e => e.DueDt < cutoff)
.ToList();
While this will return records in the next 30 days, including today:
var cutoff=DateTime.Now.AddDays(30);
List<EPCRA_Events> x = _dbContext
.Where(e => e.DueDt>=DateTime.Today && e.DueDt < cutoff)
.ToList();
This will ignore NULLs and use any indexes that cover DueDt

if you use MS Sql Server you can try this
var dateTimeNow=DateTime.Now;
var x = _dbContext.EPCRA_Events.Where(e => EF.Functions.DateDiffDay(e.DueDt, dateTimeNow) < 30).ToList();

Related

Using C# Entity Framework find a value based on 2 high low column values

If the input value is between MinIncome and MaxIncome in my database table, I want to find the Amount db field value. So for instance if an input was 1525 falling between 1500 & 1599, I would expect a return value of 75.
How do I write a Linq or Lambda statement for this? Please see screen shot.
int income = 1525;
int amount = dbContext
.MyDbSet // use DbSet Property here
.Single(x => x.MinIncome < income && x.MaxIncome > income)
.Amount;
This selects the only element where the condition is met, and returns the amount of that object.
You could also use .Where(x => x.MinIncome < income && x.MaxIncome > income).Single().Amount to achieve the exact same result.
Other options include .SingleOrDefault() which prevents errors in case no entry in your DbSet matches, .First() which prevents errors if multiple entries match, or .FirstOrDefault() if no entries or multiple entries can match.
Try this:
var result = await DbContext
.Table
.Where(x => x.MinIncome <= value && value <= x.MaxIncome)
.Select(x => x.Amount).FirstOrDefaultAsync();

Why does Linq on date range return null on a combined query, works fine on a seperated query

Issue: while filtering records within a date range & matching a CityID using LINQ, the query succeeds when written in 2 steps; however, it fails when combined as one query!
How can the LINQ query be rewritten so that -- it can perform both filters (i.e. match the CityId & retrieve records in the date range in the same step to improve performance?
I got it to work in two steps fine,
i.e. do a
var Step1 = db.weekRecord.Where(x => x.CityId == CityRecord.Id).ToList();
and then
Step1.Where(x => x.date.Date >= fromDate.Date
&& x.date.Date <= toDate.Date)
.ToList();
it fails when I combine them!!
// works when done in 2 steps!!
var weeklyWeather = db.weekRecord
.Where(x => x.CityId == CityRecord.Id
&& (x.date >= weekStarting && x.date <= weekEnding))
// - when combined results are NULL!??
var weeklyWeather2 =
db.weekRecord(x => x.date.Date >= fromDate.Date && x.date.Date <= toDate.Date)
.ToList();
After looking up other SO answers, I tried this TruncateTime as well... could not get it to work..
// is this correct, from SO answers, DbFunctions.TruncateTime
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x =>
DbFunctions.TruncateTime(x.date.Date) >= DbFunctions.TruncateTime(fromDate.Date)
&& DbFunctions.TruncateTime(x.date.Date) <= DbFunctions.TruncateTime(toDate.Date))
.ToList();
ERROR:
[NotSupportedException: The specified type member 'Date' is not
supported in LINQ to Entities. Only initializers, entity members, and
entity navigation properties are supported.]
System.Data.Entity.Core.Objects.ELinq.MemberAccessTranslator.TypedTranslate(ExpressionConverter
parent, MemberExpression linq) +452
System.Data.Entity.Core.Objects.ELinq.TypedTranslator`1.Translate(ExpressionConverter
parent, Expression linq) +49
The question is confused, but I would assume the problem is the .Date. Unlike linq2sql, entity framework can not translate .Date to sql. But you can rewrite it like
var fromDateDate = fromDate.Date;
var toDateDate = toDate.Date;
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x => DbFunctions.TruncateTime(x.date) >= fromDateDate
&& DbFunctions.TruncateTime(x.date) <= toDateDate)
.ToList();
And it would work. To some point. What EF generates is actually totally stupid in this case. Unlike linq2sql, EF generates query, that is not sargable (in my case*). It can run thousands of times slower than necessary. I would recommend to avoid the conversion to date completely:
var fromDateDate = fromDate.Date;
var toDateDate1 = toDate.Date.AddDays(1);
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x => x.date >= fromDateDate
&& x.date < toDateDate1)
.ToList();
As #juharr pointed out, when you split the query, you run first half against server and the second half as linq to objects. In that case the .Date works, but you download many more records in the first half than you need.
*the datetime type may be the problem, maybe it would work better with datetime2, I did not test this scenario
An offbest suggestion is to write your own LINQ extensions for this..
public static class ext
{
//This extension compares one date to another... if you can call from Linq
public static bool GreaterThan(this DateTime self, DateTime CompareDate
{
if (self.Year > CompareDate.Year) return true;
else if ((self.Year == CompareDate.Year) && (self.Month > CompareDate.Month)
return true;
else if ((self.Year == CompareDate.Year) && (self.Month == CompareDate.Month) && (self.Day > CompareDate.Day))
return true;
return false;
}
}
I think, there is no option except using DbFunctions.TruncateTime for Linq to Entities. Because, as SQL Server query Linq to Entities should perform convertion datetime to date and the best method which can be used is DbFunctions.TruncateTime. I just debugged the DbFunctions.TruncateTime convertion and the translated query seems like;
WHERE (convert (datetime2, convert(varchar(255), [Extent1].[CreationDate], 102) , 102)) > #p__linq__0
As you see, while performing the conversation, there is a redundant string conversation here. However, the EF would convert the datetime to date in SQL just like this 'cast(CreationDate as date)'. But it is not.
So, there are two options here.
1- If you have very huge table which the performance is affected by redundant string conversations, you should build your query manually in SQL as stored procedure or something like and execute it from context.
2- If you don't have performance considerations like that; just use DbFunctions.TruncateTime(x.date)
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x => DbFunctions.TruncateTime(x.date) >= fromDate.Date && DbFunctions.TruncateTime(x.date) <= toDate.Date)
.ToList();

Linq Query Where() with Date subtraction taking excessive time

I'm using C# and entity framework 6.
I have an entity (Animal) in my database that has a list of another entity (Feeding) attached to it. The Feeding entity has a DateTime associated with it.
My query looks like this:
var sum = animal.Feedings.Where(f => (DateTimeOffset.Now.Date- f.DateTime.Date).Days == 1).Select(f => f.Amount).Sum()
I basically want the summation of the total feeding amount from yesterday. But if my Animal object has a significantly large amount of feedings (say 5000, which is expected and could be even higher), this line is taking 20+ seconds to complete.
Is there a way to refactor this line of code to be more efficient?
EDIT
It appears to be the Feedings object being lazy loaded from the animal object.
var feedings = animal.Feedings
This line is what now takes the excessive time. The animal object was originally created from an AsQueryable() object, by selecting the animal by an ID.
EDIT #2
This logic is inside the Animal Repository and cannot access the DBContext, it can only access an IQueryable of the Animal collection. I also do not have access to EF's Include() function to try to include the feedings with the query.
The slow part is loading the Feedings collection. If it's already loaded, the LINQ to Objects query will run very fast.
If you have access to the DbContext, instead of loading the collection you can execute the query inside the database like this:
var date = new DateTimeOffset(DateTime.Today.AddDays(-1));
var sum = dbContext.Entry(animal).Collection(e => e.Feedings).Query()
.Where(f => DbFunctions.TruncateTime(f.DateTime) == date)
.Sum(f => f.Amount);
Having access to IQueryable<Animals> (as mentioned in the comments and the update) will also work. Just instead of
dbContext.Entry(animal).Collection(e => e.Feedings).Query()
you would use something like this
animals.Where(a => a.Id == animal.Id).SelectMany(a => a.Feedings)
The rest is the same.
If you have an index on DateTime, then try
var startDate = DateTimeOffset.Now.AddDays(-1).Date;
var endDate = DateTimeOffset.Now.Date;
var sum = animal.Feedings
.Where(f => (f.DateTime >= startDate && f.DateTime < endDate))
.Select(f => f.Amount)
.DefaultIfEmpty(0) // don't throw an error if the query has no results
.Sum();

Best way to compare date in Entity Framework

I am using date in where clause in Entity Framework and getting following error:
It is due to the below code:
var entity = dbContext.MyTable
.Where(w => w.PId = 3 && w.CreatedOn.Date == mydate.Date)
.First();
If I convert data into list using .ToList(), then compare to date, it will work fine but if I do so then it will pull data first into code then filter it out. Please help me out with this issue. Currently I am using a stored procedure to resolve the issue but really want this to work.
You can use DbFunctions.TruncateTime canonical function like this
var entity = dbContext.MyTable
.Where(w => w.PId == 3 && DbFunctions.TruncateTime(w.CreatedOn) == mydate.Date)
.First();
This is how I ended up doing Date search when I had to consider time (Hour and Minutes portion) also
var entity = dbContext.MyTable
.Where(
x => x.CreatedOn.Year == mydate.Year
&& x.CreatedOn.Month == mydate.Month
&& x.CreatedOn.Day == mydatee.Day
&& x.CreatedOn.Hour == mydate.Hour
&& x.CreatedOn.Minute== mydate.Minute
).FirstOrDefault();
you cannot compare dates directly by using .Date for this you have to use DbFunctions or EntityFunctions.
I prefer to use DbFunctions
You can use it as given below:
var entity = dbContext.MyTable
.Where(w => w.PId = 3 && DbFunctions.TruncateTime(w.CreatedOn) == DbFunctions.TruncateTime(mydate))
.First();
EF doesn't currently support translating all granular operations on objects in LINQ to an SQL query (which is what happens prior to that ToList() operation). Some things are (such as .Contains() on primitive collections). But not all things.
So, while you may have expected the query to translate to various "DATEPART" comparisons in the SQL statement, it's just not supported. You have to change your LINQ to accommodate it a bit.
One way is to evaluate the 24-hour range. This will work on either side of the ToList():
DateTime minDate = new DateTime(mydate.Date.Year, mydate.Date.Month, mydate.Date.Second);
DateTime maxDate = minDate.AddSeconds(86399);
var entity = dbContext.MyTable
.Where(w => w.PId = 3 && w.CreatedOn >= minDate && w.CreatedOn <= maxDate).First();

Is possible to run a query with linq to search for a period of time?

Problem details:
SQL Server 2005;
Entity Framework 4.0.
I'm trying with linq to run a query for a period of time, only. Exemple:
I have the following datetime data in my server:
30/03/2012 12:53:22
30/03/2012 17:23:29
04/04/2012 11:10:14
04/04/2012 19:06:55
I want to run a query that will return me all data between the time (12:00 and 20:00) and the query have to return to me the following data:
30/03/2012 12:53:22
30/03/2012 17:23:29
04/04/2012 19:06:55
Or between (11:00 and 13:00) and the query have to return to me the following data:
30/03/2012 12:53:22
04/04/2012 11:10:14
How can i do this with linq? Is it possible (to ignore the date, use only the time)?
var filteredTimes = myContext.MyTable
.Where(r => SqlFunctions.DatePart("hour", r.DateField) >= 11 &&
SqlFunctions.DatePart("hour", r.DateField) <= 13);
You need to include System.Data.Objects.SqlClient to get SqlFunctions.
If you convert the date value to a double and use the fractional part of if, you get a number between 0 and 1 that represents a time in a day. Having that, it is trivial to test for time intervals, where e.g. 13:45 would be 0,5729166667 (or more precise: 13:45.345 is 0,572920679).
You can do this because EF (i.e. 4.3, the version I use) translates Cast and even Math functions into SQL:
mycontext.Data.Where(dt => dt.DateTime.HasValue)
.Select(dt => dt.DateTime.Value).Cast<double>()
.Select(d => d - Math.Floor(d))
.Where(... your comparisons
This translates to Sql containing CAST([date column] AS float) and Sql's FLOOR function.
After your comments:
It looked so easy, but I can't find a way to instruct EF to do a CAST on a single property in a Select(). Only the Cast<>() function is translated to CAST (sql), but that operates on a set.
Well, fortunately, there is another way:
mycontext.Data.Where(dt => dt.DateTime.HasValue)
.Select(dt => new
{
Date = DbFunctions.TruncateTime(dt.DateTime.Value),
Ms = DbFunctions.DiffMilliseconds(
DbFunctions.TruncateTime(dt.DateTime.Value), dt.DateTime.Value)
})
.Where(x => x.Ms > lower && x.Ms < upper)
where lower and upper are the TotalMilliseconds property of TimeSpan objects.
Note that this is horribly inefficient in sql! The date functions are repeated for each comparison. So make sure that you do the comparisons on a set of data that has been confined as much as possible by other criteria. It may even be better to fetch data in memory first and then do the comparisons.
Note: prior to EF6, DbFunctions was EntityFunctions.
If you had a date and a time column in your table (as possible with SQL Server 2008), you could do this directly in SQL.
As that's not the case, you have to do it like this:
// the first two lines can be omitted, if your bounds are already timespans
var start = startDate.TimeOfDay;
var end = endDate.TimeOfDay;
var filteredItems = context.Items.ToList()
.Where(x => x.DateTimeColumn.TimeOfDay >= start
&& x.DateTimeColumn.TimeOfDay <= end);
-You can use the TimeOfDay property on the dates to compare them.
string timeAsString = "13:00";
from f in TableRows
where f.Date.TimeOfDay > DateTime.Parse("11-12-2012 "+timeAsString).TimeOfDay
select f
EDIT
here is some code you can test that runs for me:
DateTime d = DateTime.Parse("12-12-2012 13:00");
List<DateTime> dates = new List<DateTime>();
dates.Add(DateTime.Parse("12-12-2012 13:54"));
dates.Add(DateTime.Parse("12-12-2012 12:55"));
dates.Add(DateTime.Parse("12-12-2012 11:34"));
dates.Add(DateTime.Parse("12-12-2012 14:53"));
var result = (from f in dates where f.TimeOfDay > d.TimeOfDay select f);
EDIT 2
Yea it seems that you needed to .ToList(), which, tbh you should have been able to figure out. I had no way of knowing what of collection you had. Not sure either of us deserve a downvote for trying to help you when you don't supply an awful amount of information on the problem

Categories