How to apply query operation only to first entity in Azure table? - c#

I am trying my best to keep the query operation simple by just querying the first record of a table to check if my query is valid and throws no exception.
I have seen many answers on SO about retrieving top 'n' entities. My requirement here is, to apply query operation to only the first entity, no matter if that matches the filters specified in the query and abort.
I tried:
TableQuery query = new TableQuery().Where("MyKey eq 'RawMaterial'").Take(1);
But this query performs a complete table scan if there is no matching entity. Any comments on this?

Here is sample code :
List<int> numbers = Enumerable.Range(0, 100000000).Select(x => x).ToList();
DateTime startAll = DateTime.Now;
List<int> none = numbers.Where(x => x == -1).Take(1).ToList();
DateTime endAll = DateTime.Now;
DateTime startOne = DateTime.Now;
List<int> one = numbers.Where(x => x == 1).Take(1).ToList();
DateTime endOne = DateTime.Now;
Console.WriteLine("Time One : {0}, Time All {1}", endOne.Subtract(startOne).ToString(), endAll.Subtract(startAll).ToString());
Console.ReadLine();

Related

Read most recently inserted rows by Date using linq to Entity framework

I have a log table in my db and wants to fetch only those records which are added most recently based on the column name RowCreateDate, this is how I am trying to achieve the records which is bringing the rows from the db but I feel may be there is a better way to achieve the same.
using (var context = new DbEntities())
{
// get date
var latestDate = context.Logs.Max(o => o.RowCreateDate);
if(latestDate!=null)
{
lastDate = new DateTime(latestDate.Value.Year, latestDate.Value.Month, latestDate.Value.Day,00,00,00);
logs = context.Logs.Where( o.RowCreateDate >= lastDate).ToList();
}
}
What i need to know I am doing right or there would another better way?
Yet another option:
context.Logs.Where(c => DbFunctions.TruncateTime(c.RowCreateDate) == DbFunctions.TruncateTime(context.Logs.Max(o => o.RowCreateDate)))
This reads explicitly like what you want (get all rows with date equals max date) and will also result in one query (not two as you might have expected).
You can't simplify this code because LINQ to Entities does not support TakeWhile method.
You can use
using (var context = new DbEntities())
{
// get date
var latestDate = context.Logs.Max(o => o.RowCreateDate);
if(latestDate!=null)
{
lastDate = new DateTime(latestDate.Value.Year, latestDate.Value.Month, latestDate.Value.Day,00,00,00);
logs = context.Logs
.OrderBy(o => o.RowCreateDate)
.AsEnumerable()
.TakeWhile(o => o.RowCreateDate >= lastDate);
}
}
BUT it takes all your data from DB, which is not very good and I do not recommend it.
I think this will do (if we assume you want to get top 3 most recent record):
var topDates = context.Logs.OrderByDescending(x=>x.RowCreateDate).Take(3)
First, I think that your code is fine. I don't see the problem with the two queries. But if you want to simplify it you use TruncateTime, like this:
IGrouping<DateTime?, Logs> log =
context.Logs.GroupBy(x => DbFunctions.TruncateTime(x.RowCreateDate))
.OrderByDescending(x => x.Key).FirstOrDefault();
It will return a grouped result with the logs created during the last day for RowCreateDate.

LINQ to entites does not recognize Convert.ToDatetime method

EDIT All the other answers that link to a previous question's wont work for me because they are either using two tables or they know what startdate they are looking for.
I have the following LINQ query where i am trying to get the data from a table for the last 14 days. LINQ does not recognize Convert.ToDatetime method. The Query_Date column is a type string and i can't change it. How do i get the data i need?
var logs = (from bwl in db.UserActivities
where Convert.ToDateTime(bwl.Query_Date) >= DateTime.Now.AddDays(-14)
select new
{
Id = bwl.Id,
UserEmail = bwl.UserEmail
}).ToList();
So i couldn't get what i wanted because i would have to make too many changes so the workaround i am doing is: I am using this code
var logs = db.UserActivities.ToList().Take(100);
This will get me the last 100 entries. I will give options for more or less entries then they can filter it on date in the search bar on the datatable.
It's not great but time is against me and this will suffice.
Do you have data being entered at least every day? If so, what about something like this:
var oldestDate = DateTime.Now.AddDays(-14);
var dateString = oldestDate.ToString(*/ your date format */);
var oldestID = db.UserActivities.First(b => b.Query_Date == dateString).Id;
var logs = (from bwl in db.UserActivities
where bwl.Id > oldestID
select new {
Id = bwl.Id,
UserEmail = bwl.UserEmail
}).ToList();
Basically you find a record on that date and use its ID as a proxy for the date. This only works in certain circumstances (i.e. if the IDs are in date order).
This also isn't guaranteed to be the oldest entry on that date, but that could be achieved either by using ID order:
var oldestID = db.UserActivities.Where(b => b.Query_Date == dateString)
.OrderBy(b => b.Id)
.First().Id;
Or more lazily by using -15 instead of -14 when you add days, if you don't mind grabbing an unknown percentage of that 15th day.
Convert.ToDateTime works if your Query_Date has proper format. If not, check your string expression is convertable format.
I tested below code and it works fine when I assume Query_Date is a form of DateTime.toString().
var logs = (from bwl in db.UserActivities
where DateTime.Now - Convert.ToDateTime(bwl.Query_Date) <= TimeSpan.FromDays(14)
select new
{
Id = bwl.Id,
UserEmail = bwl.UserEmail
}).ToList();
I also tested with your where expression Convert.ToDateTime(bwl.Query_Date) >= DateTime.Now.AddDays(-14) and confirmed that it gives same result.

Select if criteria is met

Lets say I have a datatable with three columns: timestamp, curveID and price. I would like to give a time and then select for each day the timestamp, curveID and price but only if all curveIDs are present.
The problem is, not for every time all the data is present, so at 10:00:00 there might be only data for curveID 1 but nothing for ID =2, and so forth.
I thought I could do the following to select the first dataset where all curveIDs are there and time is greater or equal to my criteria:
dataSet.ReadXml(#"C:\temp\Prices.xml", XmlReadMode.InferTypedSchema);
ds = dataSet.Tables[0];
var dt = ds.Clone();
int criteria = 10;
var list = ds.AsEnumerable().Where(x => x.Field<DateTime>("Timestamp").Hour >= criteria)
.GroupBy(x => new{Date = x.Field<DateTime>("Timestamp").Date, Curve = x.Field<object>("CurveID")})
.First().ToList();
However, this returns multiple records on the same day (at different times) for the same curve ID.
I would like to return only a single record for each curveID on each day at a time close to the criteria time where all curveIDs are present.
For clarity, lets say I m looking for curveID 1 & 2, if at 10:00:00 on day 1 only curveID 1 is present but curveID 2 is missing I would need to check whether at 10:01:00 both are there, if yes I take for that day the two record sets from that time. This I would have to check for every day in the database
// criteria is your integer Hour representation
var criteria = 10;
// array of curveIds to look for
var curveIds = new int[] {1, 2};
var result =
// grouping by date first
ds.GroupBy(x => x.Field<DateTime>("Timestamp").Date,
(date, items) => new { date, items = items
// items with the same timestamp go to one group
.GroupBy(i => i.Field<DateTime>("Timestamp"), (datetime, timestampItems) => new { datetime, timestampItems })
// filter by criteria
.Where(dti => dti.datetime.Hour >= criteria)
// filter by curveIds
.Where(dti => curveIds.All(cid => dti.timestampItems.Any(tsi => tsi.Field<int>("curveID") == cid)))
.OrderBy(dti => dti.datetime)
.FirstOrDefault() });
In the end you will receive a "per day" result fitting all your mentioned requirements: occurs after some criteria, have all curveIds, be earliest one.
You may want to group by Date first and then by hour using something like
group thing by new {
firstThing = x.Field<DateTime>("TimeStamp").Date,
secondThing = x.Field<DateTime>("TimeStamp").Date.Hour,
}
My syntax is probably off by a little, but that should get you moving in the right direction

Is it possible to assign single linq query result to more than one variable?

I can do this in TSQL
SELECT
#TotalDays = COUNT(Days),
#TotalHours = SUM(Hours)
FROM
Schedule
WHERE
GroupID = 1
How to achieve this in linq in single query, my current code;
var totalDays = 0;
var totalHours = 0;
totalDays = _schedule.Count(c => c.GroupID == 1);
totalHours = _schedule.Where(w => w.GroupID == 1).Sum(s => s.Hours);
This is not effective because it call 2 separate queries in the database
You could try something like this:
var result = _schedule.Where(s => s.GroupID == 1)
.GroupBy(x => x.GroupID)
.Select(gr => new
{
TotalDays = gr.Count(),
TotalHours = gr.Sum(s=>s.Hours);
});
Initially, you filter your data based on the GroupID. You pick those with GroupID equals to 1. Then you GroupBy them by their ID. This mihgt seams a bit silly, but this way you create a group of your data. So then you count just count the item in the group and calculate the sum you want. Last but not least after having made the GroupBy, you select an anonymous type with two properties, one for the TotalDays and one for the TotalHours.
Then you can consume the above result as below:
var totalDays = 0;
var totalHours = 0;
var first = result.FirstOrDefault();
if(first!=null)
{
totalDays = first.TotalDays,
totalHours = first.TotalHours
};
The problem, sometimes, trying to make a single LINQ query is that it actually gets translated into multiple database calls. Sometimes it is better to pull all of your raw data into memory in a single database call and then perform the calculations.
This will ensure only one database call:
var data = _schedule.Where(w => w.GroupID == 1).Select(w => w.Hours).ToArray();
var totalDays = data.Count();
var totalHours = data.Sum();
The key to making this work is the .ToArray() which forces the evaluation of the database query. If there are a lot of items this call can become inefficient, but in lot of cases it is still very fast.
You can use the next code
//one request to data base
var lstResult=_schedule.Where(w => w.GroupID == 1).ToArray();
//one loop in array for Count method
totalDays = lstResult.Count();
//One loop in array for Sum method
totalHours = lstResult.Sum(s => s.Hours);

Linq if DateTime field is older than X hours

I have tried the following (obviously without the //), but I can't get any to work, can anybody help please?
public void CleanBasket()
{
//double validHours = 3;
// var expired = (from a in db.Baskets where (DateTime.Now - a.DateCreated).TotalHours > validHours select a);
//var expired = (from a in db.Baskets where (DateTime.Now.Subtract(a.DateCreated).Hours > 3) select a);
//var expired = (from a in db.Baskets where(a => a.DateCreated > DateTime.Now.AddHours(-1));
//foreach (Basket basket in expired) db.DeleteObject(expired);
db.SaveChanges();
}
In this case surely you can simply do your date time calculation before you invoke LINQ:
double validHours = 3;
var latest = DateTime.UtcNow.AddHours(-validHours);
var expired = (from a in db.Baskets where a.DateCreated < latest select a);
For any more complex DateTime operations that you need to do in the database and cannot do this way you can use SqlFunctions.
BTW you should store your times in Utc not local time. Calculations using DateTime.Now will be wrong during daylight savings time changes.
My guess is that linq-to-entities doesn't know how formulate the query with the DateTime.Now operation. To do this, I would get the values in a list and then with just linq, filter them out.
public void CleanBasket()
{
var cutoff = DateTime.Now.Subtract(new TimeSpan(3, 0, 0));
var baskets = db.Baskets.Where(a=>a.DateCreated<cutoff);
db.DeleteObjects(baskets); // You can combine this with the last line
db.SaveChanges();
}
It is very likely that a ORM wont be able to translate TimeSpan operations, check this question it could be helpful: Comparing dates in query using LINQ
basically you can consider that if the method you are using does not have a literal translation to SQL it is very likely that it will not be supported.

Categories

Resources