LINQ. Grouping by days. How to do this easily? - linq

I can't seem to find any good reference on this. I have a lot of data in SQL with dates. So I wanted to make a line chart to show this data over time. If I want to show it over a period of days then I need to group by days.. But the LOGDATE is the full date.. not the DAY..
So I have this below, but LINQ doesn't know what 'DayOfYear' property is...
var q = from x in dc.ApplicationLogs
let dt = x.LogDate
group x by new { dayofyear = dt.Value.DayOfYear } into g
select new
{
iCount = g.Count(),
strDate = g.Key
};

In EF core you can use DateTime.Date to get the date portion of a DateTime value.
In EF6 you can use DbFunctions.TruncateTime:
to return the given date with the time portion cleared
var q = from x in dc.ApplicationLogs
let dt = x.LogDate.Date
// EF6:let dt = DbFunctions.TruncateTime(x.LogDate)
group x by dt into g
select new
{
iCount = g.Count(),
strDate = g.Key
};

You want .Date to get the date part of a DateTime not DayOfyear unless you are deliberately trying to put the same day of the year from each year into the group.

Why are you using:
let dt = x.LogDate
group x by new { dayofyear = dt.Value.DayOfYear } into g
instead of just:
group x by x.LogDate.Value.DayOfYear into g
I'm not sure about this, but it's possible using an anonymous object like that in your group by clause is messing up L2S.

Here you go
let d = new DateTime(n.time.Year, n.time.Month, n.time.Day) group n by d into grp select grp

Related

How to make zero counts show in LINQ query when getting daily counts?

I have a database table with a datetime column and I simply want to count how many records per day going back 3 months. I am currently using this query:
var minDate = DateTime.Now.AddMonths(-3);
var stats = from t in TestStats
where t.Date > minDate
group t by EntityFunctions.TruncateTime(t.Date) into g
orderby g.Key
select new
{
date = g.Key,
count = g.Count()
};
That works fine, but the problem is that if there are no records for a day then that day is not in the results at all. For example:
3/21/2008 = 5
3/22/2008 = 2
3/24/2008 = 7
In that short example I want to make 3/23/2008 = 0. In the real query all zeros should show between 3 months ago and today.
Fabricating missing data is not straightforward in SQL. I would recommend getting the data that is in SQL, then joining it to an in-memory list of all relevant dates:
var stats = (from t in TestStats
where t.Date > minDate
group t by EntityFunctions.TruncateTime(t.Date) into g
orderby g.Key
select new
{
date = g.Key,
count = g.Count()
}).ToList(); // hydrate so we only query the DB once
var firstDate = stats.Min(s => s.date);
var lastDate = stats.Max(s => s.date);
var allDates = Enumerable.Range(1,(lastDate - firstDate).Days)
.Select(i => firstDate.AddDays(i-1));
stats = (from d in allDates
join s in stats
on d equals s.date into dates
from ds in dates.DefaultIfEmpty()
select new {
date = d,
count = ds == null ? 0 : ds.count
}).ToList();
You could also get a list of dates not in the data and concatenate them.
I agree with #D Stanley's answer but want to throw an additional consideration into the mix. What are you doing with this data? Is it getting processed by the caller? Is it rendered in a UI? Is it getting transferred over a network?
Consider the size of the data. Why do you need to have the gaps filled in? If it is known to be returning over a network for instance, I'd advise against filling in the gaps. All you're doing is increasing the data size. This has to be serialised, transferred, then deserialised.
If you are going to loop the data to render in a UI, then why do you need the gaps? Why not implement the loop from min date to max date (like D Stanley's join) then place a default when no value is found.
If you ARE transferring over a network and you still NEED a single collection, consider applying D Stanley's resolution on the other side of the wire.
Just things to consider...

How to query and calculate dates in the where clause of a LINQ statement?

I am having trouble with the following piece of code. Before I paste it, Let me give a bit of history on what should happen.
I have a model containing 2 fields of interest at the moment, which is the name of the order the customer placed, and the date at which he/she placed it. A pre-calculated date will be used to query the dateplaced field (and should only query the dates , and not the time). The query counts the amount of duplicates that occur in the MondayOrder field, and groups them together. Now , when I exclude the where clause which should query the dates, the query runs great. However, The goal of this query is to count the amount of orders for the following week based on the date the order has been placed.
List<string> returnlist = new List<string>();
DateTime dt = getNextWeekMondaysDate().Date;
switch (day)
{
case DayOfWeek.Monday:
{
var CountOrders =
from x in Data.EntityDB.Orders
group x by x.MondayOrder into m
let count = m.Count()
select new
{
MondayOrderItem = m.Key, Amount = count
};
foreach (var item in CountOrders)
{
returnlist.Add(item.MondayOrderItem + " : " +
item.Amount);
}
}
break;
The getNextWeekMondaysDate() method has an overload which I can use, where if I supply it a date, it will get the following Monday's date from the parameter given. The problem is though, LINQ does not accept queries such as the following:
var CountOrders =
from x in Data.EntityDB.Orders
where getNextWeekMondaysDate(x.DatePlaced.Value).Date == dt
group x by x.MondayOrder into m
let count = m.Count()
select new { MondayOrderItem = m.Key, Amount = count };
This is exactly what I must achieve. Is there any workaround for this situation?
UPDATE
Here is the exception I get when I try the 2nd query.
LINQ to Entities does not recognize the method 'System.DateTime getNextWeekMondaysDate(System.DateTime)' method, and this method cannot be translated into a store expression.
You cannot do this directly, as user-defined method calls cannot be translated to SQL by the EF query provider. The provider recognizes a limited set of .NET methods that can be translated to SQL and also a number of canonical functions as well. Anything that cannot be expressed using these methods only is off-limits unless you write your own query provider (which is only theoretically an option).
As a practical workaround, you can calculate an appropriate range for x.DatePlaced.Value in code before the query and then use specific DateTime values on the where clause.
As an intellectual exercise, note that this method is recognized by the query provider and can be used as part of the expression. So this abomination should work too:
var CountOrders =
from x in Data.EntityDB.Orders
where EntityFunctions.AddDays(
x.DatePlaced.Date.Value,
(9 - DateAndTime.DatePart(DateInterval.WeekDay, x.DatePlaced.Value)) % 7)
.Date == dt
group x by x.MondayOrder into m
let count = m.Count()
select new { MondayOrderItem = m.Key, Amount = count };
Linq to Entities doesn't know how to convert arbitrary C# methods into SQL - it's not possible in general.
So, you have to work with the methods it does understand.
In this case, you could do something like this:
DateTime weekBegin = CalculateWeekBegin( dt );
DateTime weekEnd = CalculateWeekEnd( dt );
var CountOrders =
from x in Data.EntityDB.Orders
where x.DatePlaced.Value >= weekBegin && x.DatePlaced.Value < weekEnd
group x by x.MondayOrder into m
let count = m.Count()
select new { MondayOrderItem = m.Key, Amount = count });

BLToolKit, Linq Query, SQL Not what I expected

I am using BLToolKit in a project of mine and I was trying to get this to work. What I don't like is that I am trying to average a bunch of temps down to the minute, but the select statement that is being generated groups by the minute but then selects the original time. I think I am doing the linq expression correctly (but then again, i am not getting the results i expect). (this is C#, if you care) Anyone know what is going wrong?
var test = (from r in db.SensorReadingRaws
where r.TimeLogged < DateTime.Now.AddMinutes(-2)
group r by new
{
Sensor = r.SensorNumber,
//group time down to the minute
Time = r.TimeLogged.AddSeconds(-1 * r.TimeLogged.Second).AddMilliseconds(-1 * r.TimeLogged.Millisecond)
} into grouped
select new SensorReading
{
SensorNumber = grouped.Key.Sensor,
TimeLogged = grouped.Key.Time,
Reading = (int)grouped.Average(x => x.Reading)
}).ToList();
textBox1.Text = db.LastQuery;
and the resulting query is this
SELECT
[r].[SensorNumber],
[r].[TimeLogged],
Avg([r].[Reading]) as [c1]
FROM
[SensorReadingRaw] [r]
WHERE
[r].[TimeLogged] < #p1
GROUP BY
[r].[SensorNumber],
DateAdd(Millisecond, Convert(Float, -DatePart(Millisecond, [r].[TimeLogged])), DateAdd(Second, Convert(Float, -DatePart(Second, [r].[TimeLogged])), [r].[TimeLogged])),
[r].[TimeLogged]
I discovered that
BLToolkit.Data.Linq.Sql.AsSql<T>(T obj)
can be used as a workaround for this case.
When applying this function to the required grouped key properties in select statement you get rid of grouping/selecting an original field.
It may look something like:
_queryStore.Leads().
GroupBy(x => new {
x.LeadDate.Hour,
x.LeadDate.Minute
}).
Select(x => new {
Hour = Sql.AsSql(x.Key.Hour),
Minute = Sql.AsSql(x.Key.Minute),
Count = x.Count()
});
and in your particular case:
var test = (from r in db.SensorReadingRaws
where r.TimeLogged < DateTime.Now.AddMinutes(-2)
group r by new
{
Sensor = r.SensorNumber,
//group time down to the minute
Time = r.TimeLogged.AddSeconds(-1 * r.TimeLogged.Second).AddMilliseconds(-1 * r.TimeLogged.Millisecond)
} into grouped
select new SensorReading
{
SensorNumber = grouped.Key.Sensor,
TimeLogged = Sql.AsSql(grouped.Key.Time),
Reading = (int)grouped.Average(x => x.Reading)
}).ToList();
I got same issue yesterday.
Today I found a workaround. The idea is to write 2 linq queries. First transforming the data and the second grouping the result:
var bandAndDate =
(from r in repo.Entities
select new {Band = r.Score / 33, r.StartTime.Date});
var examsByBandAndDay =
(from r in bandAndDate
group r by new {r.Band, r.Date } into g
select new { g.Key.Date, g.Key.Band, Count = g.Count() }).ToList();
Both this queries run one SQL that do the job:
SELECT
[t1].[c1] as [c11],
[t1].[c2] as [c21],
Count(*) as [c3]
FROM
(
SELECT
[r].[Score] / 33 as [c2],
Cast(Floor(Cast([r].[StartTime] as Float)) as DateTime) as [c1]
FROM
[Results] [r]
) [t1]
GROUP BY
[t1].[c2],
[t1].[c1]

Linq group by month

I have a table with a date field and time field.I want to retrieve a result set grouped by the month and the number of records that appear within that month. How can this be done in LINQ?
Something like this.
Data.
10/10/2011 00:00:10:000
10/11/2011 00:00:20:000
11/01/2011 00:00:10:000
11/10/2011 00:00:40:000
I want to retrieve
10/1/2011 00:00:30:000
11/1/2011 00:00:50:000
Thank`s for help.
i am try something like this.
var monthely =
from u in sqlDataContract.TimeTrickTables
group u by u.EntryDate.Value.Month into g
select new
{
EntryDate = g.Key,
ETime = g.Aggregate(TimeSpan.Zero, (subtotal, t) => subtotal.Add((TimeSpan)t.ETime))
};
but it throw this Exception The query operator 'Aggregate' is not supported
from u in TimeTrickTables
group u by u.EntryDate.Value.Month into g
select new
{
Month = g.Key,
Count = g.Count(), //Nr. of records
Time = new TimeSpan(g.Sum(x => x.ETime.Ticks)) //Total time
}

Group By Dates using Linq

What I have is a set of users with join dates and I want to use GoogleChartSharp to create a chart showing how many users have joined each month.
So I have this set of dates (US format) in a list:
01/01/2009
02/01/2009
02/12/2009
03/02/2009
03/12/2009
03/22/2009
Googlechartsharp requires me to do something like this:
string[] monthYears = new string[] { "01/2009", "02/2009", "03/2009"};
int[] number= new int[] {1,2,3};
LineChart chart = new LineChart(150, 150);
chart.SetData(data);
chart.SetLegend(monthYears);
string url = chart.GetUrl();
How do I utilize linq to get the list of dates into arrays required by google? Or is Linq even the right tool?
Sure, Linq is an ideal tool. The code below is an example. (I have specified the month in the dates so I don't have to use DateTime.ParseExact when setting up the input array).
DateTime[] dates =
{
DateTime.Parse("jan/01/2009")
,DateTime.Parse("feb/01/2009")
,DateTime.Parse("feb/12/2009")
,DateTime.Parse("mar/02/2009")
,DateTime.Parse("mar/12/2009")
,DateTime.Parse("mar/22/2009")
};
var datesGroupedByMonthYear = from date in dates
group date by date.ToString("MM/yyyy") into groupedDates
orderby groupedDates.First() ascending
select new { MonthYear = groupedDates.Key, Dates = groupedDates };
string[] monthYears = (from d in datesGroupedByMonthYear select d.MonthYear).ToArray();
int[] number = (from d in datesGroupedByMonthYear select d.Dates.Count()).ToArray();
Asusming those numvers are the number of times each month occurs. Then you could do something like this:
var numbers = from d in monthYears .Select(x => DateTime.Parse(x))
group d by d.Month into dg
select dg.Count();
That will give you an IEnumerable of the count for each. If you need it as an array it is not too hard to convert that to an array.

Resources