I have a database field like this:
Timestamp varchar(19) utf8mb4_unicode_ci
containing a timestamp (string) like this
"2013-05-29 00:00:00"
I am using the entity framework and I would like to filter by time - meaning I would like to get all entries having a timestamp > (now-interval).
Code would look something like this
var query = from r in db.route
where
r.timestamp > (now-interval);
select r;
How can I do this?
My first suggestion would be to fix the database so the date values are stored as the correct date type. That would solve many issues as well as increase search performance. However, in the (unlikely) situation that you are unable to do that, and that the format of the values in that column all match exactly as you specified in the question, you could convert your local time stamp variable to a string and compare it directly. As the format you have shown has an alphanumeric ordering that is identical to the date order, it should work:
//It's important that this is done here and not inline with the Linq query
var nowInterval = timeStamp.ToString("yyyy-MM-dd HH:mm:ss");
var query = from r in db.route
where string.Compare(r.timestamp, nowInterval, StringComparison.Ordinal) > 0
select r;
Related
I have a SQL query that needs to average many datetime values server-side (in SQL Server). For example purposes, let's just consider it's a simple query like this on a table with millions of records:
SELECT
SomeField,
AVG(CAST(ADateTime AS decimal(18,8))) AS NumericRepresentation
FROM MyTable
GROUP BY SomeField
As shown, I can't simply take AVG(ADateTime) because SQL Server isn't happy with doing that, but converting it to a Decimal (and later converting it back to a DateTime) works well enough for me.
The obvious way to do something comparable with EntityFramework is to use .Select(tbl => tbl.ADateTime.Ticks).Average(), but this fails at runtime because DateTime.Ticks doesn't translate through Linq-to-Entities.
How should I best go about this? My main need is to average datetime values in some way. The temporary representation (decimals, ticks, etc) isn't terribly important as long as it can be translated back to a DateTime either in SQL or .NET code. What is important, though, is that the averaging is done in SQL Server (I have some fairly complex calculations with this over many records) and I can somehow have the translated DateTime in .NET (whether the translation happens in SQL Server or in .NET, I don't care).
In pure SQL you can do average on a date field with something like this:
-- the smallest date you could possibly have in your data
DECLARE #MinDate DATE = '1/1/1900'
SELECT
SomeField,
DATEADD(DAY, AVG(DATEDIFF(DAY, #MinDate, ADateTime)), #MinDate) as AvgDateTime
FROM MyTable
GROUP BY SomeField
Not sure yet how to translate this to LINQ :)
UPD: Here is the LINQ code:
private static void Test(IQueryable<SomeClass> data)
{
var minDate = DateTime.MinValue;
var avgMilliseconds = data.Select(x => x.SomeDateField.Subtract(minDate).TotalMilliseconds).Average();
var avgDate = minDate.AddMilliseconds(avgMilliseconds);
Console.WriteLine(avgMilliseconds);
Console.WriteLine(avgDate);
}
EF LINQ expressions can make use of SqlFunctions class to make sure the conversion happens correctly.
DateTime minDate = new DateTime(1900,1,1);
var avg = MyTable.Select(myrow => SQLFunctions.DateDiff("second",myrow.ADateTime,minDate).Average();
DateTime avgDate = minDate.AddSeconds(avg);
Previous answer, should be disregarded:
Use Convert.ToDouble. EntityFramework should be able to translate this LINQ to SQL is able to CONVERT(float,...) as long as your column is actually a DateTime and not DateTime2 or DateTimeOffset, but unfortunately Entity Framework is not able to recognize this construct.
.Select(tbl => Convert.ToDouble(tbl.ADateTime)).Average()
An alternate choice is to do it client side:
.Select(tbl => tbl.ADateTime).ToArray().Select(dt => dt.Ticks).Average()
though clearly that's not preferred if you're averaging millions of rows.
My query looks like so:
using (var ctx = new PCLvsCompContext())
{
var brokerId = broker?.Id;
var symbolId = company?.Id;
var result = (from t in ctx.TradeHistoryJoineds
where t.TradeDate >= fromDate
&& t.TradeDate <= toDate
&& (brokerId == null || t.BrokerId == brokerId)
&& (symbolId == null || t.SymbolId == symbolId)
select t).OrderBy(x => x.TradeDate).ThenBy(x => x.BrokerName).ToList();
return result;
}
As an example, I run this query with dates like fromDate March-01-2017 toDate March-31-2017. I then captured the generated sql in SQL profiler that this query produces and ran it in SQL management studio. The output was as expected where for each weekday, each company has some trades. The query is based off of a view which casts all dates to "datetime" so that excel can parse them as dates correctly. However, when I put a breakpoint at "return result" and inspect the dates, all but 2 of the dates are March-1-2017. This is incorrect, the query result in SQL manager shows trades for almost every weekday in March (which is correct).
What is going on here? Why is Linq losing its mind?
Although based on the results I cannot see exactly how you would end up with those results, it is very common that you could be dealing with a DateTime timezone issue. I suspect that perhaps you saved your dates to the database using a DateTime object from say DateTime.Now instead of DateTime.UtcNow. So at that point in time and based on the machine it was called on it would be based on the timezone and datelight savings of that machine.
A DateTime object should not be used as it can relate to the region of the SQL database, the region of the server making this LINQ call and so the two regions could be on different timezones.
Instead you should always use DateTimeOffset.
If you cannot do that for some reason, then double-check your dates toDate and fromDate and do:
var utcToDate = toDate.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
var utcFromDate = toDate.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
Which gives something like this if it was run on 3rd April 2018 at 22:56.
2018-04-03T22:56:57.740Z
You would then also need to make sure when you save any date to the SQL backing store that you do ToUniversalTime() firstly. You can check your SQL tables directly with a normal SQL query and they should be stored in the database as the same UTC string format as above, and then the comparison should be obvious to whether it is valid.
However I would strongly recommend changing all your DateTime calls and gets to DateTimeOffset. The only time to use DateTime in majority of cases is for the final display to a user.
Thank you all for your suggestions. For those who are familiar with linq, EF and views this may seem like a stupid oversight, but I will post my shame for others in case this happens to them since the cause and the resulting behavior are not immediately obviously linked (as can be seen by all the suggestions, none of which actually point to the answer).
When you are querying a view using linq and Entity Framework, you apparently must have a column called 'Id', otherwise Entity Framework can't distinguish any of the rows from one another and simply makes some sort of duplication that I can't quite decipher (all the rows were unique based on their data even without an Id column, so I can't quite figure out why this is necessary).
So by adding an the TradeId with an 'Id' alias to the view, then Entity Framework seemed to return to sanity and map the data as expected.
I have a collection with subcollections in it, one of which is a date field stored in the DB as a string in the format yyyymmdd (which also contains a few random things such as "E" or 20085, etc.). This date is also part of one of the subcollections in the collection. This date field will now be used for searching, so I need to make it into a real date to use it in LINQ statements.
I since learned that LINQ to SQL doesn't support statements that it cannot translate into SQL, so I can't insert a function that returns a properly converted date and I haven't yet found a standard convert function that will transform the string into a valid date object.
I also tried layered converting, though this smelled bad, such as this:
search = from c in search
where c.Object.Any(p=> new DateTime(Convert.ToInt32(p.theDate, Substring(0,4))))... etc.
I just received conversion errors no matter what I did here.
My basic question is: does LINQ to SQL support anything inline for such a conversion? Another option is to change the datamodel or make a view, but I'd rather see if there are ways to handle this in code first. What I'm trying to do is something like the following:
search = from c in search
where c.subcollection.Any(p=>p.theDate >= min) && c.subcollection.Any(p=>p.theDate <= max)
select c;
Where min and max are passed in date values.
Thank you!
The SqlMethods class has some static helper methods that are translatable by LINQ to SQL. Most of these have to do with DateTime comparison. I'm not sure exactly which method you are looking for, but you should be able to find what you want at the documentation page for System.Data.Linq.SqlClient.SqlMethods
Alternatively, you could create a user-defined function and handle the conversion and comparison issue on the SQL side. The integration doesn't require much more than decorating the corresponding Object Model method with a FunctionAttribute, (which indicates how LINQ-to-SQL should process the method. Here is some documentation on this process:
How to: Call User-Defined Functions Inline
User-Defined Functions
If you wish to compare a date using LINQ but the SQL date column is a string value, use .CompareTo("2002-01-01") >= 0
This will translate and return any dates that match 2002-01-01 or occured later in time. Not sure how this works in with your subcollection but as an example:
search = from c in search
where c.stringDateColumn.CompareTo("2002-01-01") >= 0
select c;
Edit: I did some tests on a table with dates in strings like "2002-09-04E", "2004-04-01 34", "20050604e" and "2005-06-01444" and the results do return based on a string in format yyyy-mm-dd or yyyymmdd:
search = from c in search
where c.subcollection.Any(p=>p.theDate.CompareTo(min) >= 0) && c.subcollection.Any(p=>p.theDate.CompareTo(max) <= 0)
select c;
I have a database with a ValidDate field - it's a string(we made a mistake, it should be a datetime, but we can't modify the database now.)
and now I want to compare this filed with a parameter(validDateStart) from the website:
priceList = priceList.Where(p => Convert.ToDateTime(p.ValidDate) >= Convert.ToDateTime(validDateStart));
var list = initPriceList.ToList();
But I get an error: The method ToDateTime is not implemented.
Can anybody give me some help? Thanks!
This is not supported in Linq to Entities (nor Linq to SQL to my knowledge). Remember that your query is executed on the database - where there is simply no equivalent for Convert.ToDateTime.
Any string parsing in your query would really just be a workaround - as a real solution make those columns not strings but datetime in the database and you would not have this problem in the first place.
A hacky workaround would be materializing all rows (you can use AsEnumerable() for that), then doing the parsing - this will have bad performance though but might work good enough if there are few rows:
var startDate = DateTime.Parse(validDateStart);
var list = priceList.AsEnumerable()
.Where(p => DateTime.Parse(p.ValidDate) >= startDate);
.ToList();
Edit:
With your example update it looks like you can just do string comparisons to do what you wanted - granted it's still a hack but would perform much better than materializing all rows. This is possible because your date format puts the most significant numbers first, then the less significant parts - it's year, then month, then day (should this not be the case and the day comes before the month in your example this solution will not work).
Assuming your input string validDateStart is in the same format as well you can just do:
var list = priceList.Where(p => p.ValidDate.CompareTo(validDateStart) >=0);
.ToList();
string comparison with String.CompareTo seems to be support both in Linq to Sql as well as Linq to Entities.
If all the records in your database always start with year, month and day (for example: the date format is yyyy-MM-dd HH:mm:ss or yyyy/MM/dd or yyyyMMdd) no matter if it has separators or not. The thing is that the values should has a format where it starts with year, month and day.
You can do the following:
1: Convert your filter value (website) to the same format as you have in your database:
// DateTime format in database: yyyy-MM-dd HH:mm:ss:ffffff
var from = filtro.CreationDateFrom?.ToString("yyyy-MM-dd");
var to = filtro.CreationDateTo?.AddDays(1).ToString("yyyy-MM-dd");
2: And write your query like this (using CompareTo method):
var query = (from x in ctx.TskTaskQueues
where x.CreationDatetime.CompareTo(from) >= 0
&& x.CreationDatetime.CompareTo(to) <= 0
select x);
It worked for me!
I'm not using LinqToEntities but I'm using LinqConnect (for Oracle) that is similar to LinqEntities.
If you use a format like this dd-MM-yyyy, it probably will not work.
I have a problem while comparing smalldatetime/datetime column value from DB(SQL Server 2008) with DateTime.Now in C#.
I know that datetime format in DB and on the server on which application is running are different, so I have done something like this to "cache" the results so date time comparison will be local and not be on server:
var inactiveAccounts = (from entry in ent.Accounts
where entry.Status == 0
select entry).ToArray();
var accountsFiltered = (from entry in inactiveAccounts
where entry.DeactivationDate < DateTime.Now
select entry).ToArray();
And at some pertiod of day I am not getting the right records.
I suspect it is due to where entry.DeactivationDate < DateTime.Now date comparison. And seems in EF it is tricky to work with datetime, please help me to identify the problem.
DonĀ“t know if it solves your problem but i would create a local variable and set DateTime.Now on it. Then use the local variable in your linq query.
This is done, because DateTime.Now gets evaluated in your query each time the where clause is called. So for each entry in inactiveAccounts you are working against another DateTime.
Also if you want to compare Dates without the Time value, you should use DateTime.Today
var inactiveAccounts = (from entry in ent.Accounts
where entry.Status == 0
select entry).ToArray();
DateTime currentDateTime = DateTime.Now;
var accountsFiltered = (from entry in inactiveAccounts
where entry.DeactivationDate < currentDateTime
select entry).ToArray();
You can use this directly:
var inactiveAccounts = (from entry in ent.Accounts
where entry.Status == 0 && entry.DeactivationDate < DateTime.Now
select entry).ToArray();
Because DataTime.Now and DateTime.UtcNow are translated as canonical functions = they should not be evaluated on .NET side but translated toGETDATE() or GETUTCDATE() SQL function calls respectively.
The rest of your question is unanswerable because providing information like
And at some period of day I am not getting the right records.
You must put much bigger effort to analysis of the problem if you want to get answer including which periods cause problem, what timezones are used, etc. Generally we will not help you with that because we don't see your data. It is also not a problem of EF because in your case it happends completely in linq-to-object = plain .NET code.