LINQ How to Show Average of all results in GroupBy - c#

I am trying to write a query which returns the average time spent on a job in a ticketing system.
There are multiple time logs in each job so I need to group by the SO Number and then somehow get an average of all of the results.
The current query returns a list of every service order and the total minutes spent to the job.
How do I make this show me the average minutes spent on each job?
from so in TblServiceOrders
from sologs in TblSOLogs.Where(x => x.SONumber == so.SONumber).DefaultIfEmpty()
where so.DateClosed >= new DateTime(2013,07,01)
where so.DateClosed <= new DateTime(2013,07,02)
where sologs.ElapsedHours != 0 || sologs.ElapsedMinutes != 0
group new { sologs.ElapsedHours, sologs.ElapsedMinutes } by so into g
select new {
g.Key.SONumber,
elapsed = g.Average (x => (x.ElapsedHours == null ? 0 : x.ElapsedHours * 60) + (x.ElapsedMinutes == null ? 0 : x.ElapsedMinutes))
}
==EDIT==
This looks like it is getting close but it is giving me an average of every time log and not an average of the total time logs in each SO.
Please help?
from so in TblServiceOrders
join sologs in TblSOLogs on so.SONumber equals sologs.SONumber
where so.DateClosed >= new DateTime(2013,07,01)
where so.DateClosed <= new DateTime(2013,07,03)
where sologs.ElapsedHours != 0 || sologs.ElapsedMinutes != 0
group sologs.SONumber by sologs into g
group new {g.Key.ElapsedHours, g.Key.ElapsedMinutes} by "Total" into t
select t.Average (x => (x.ElapsedHours == null ? 0 : x.ElapsedHours * 60) + (x.ElapsedMinutes == null ? 0 : x.ElapsedMinutes))

I've worked it out for the most part, Hopefully this can help others.
The first part of my question was getting all the results into the same group without a distinct value to Group By. This was achieved by declaring a string in the Group By such as "Total".
For Example:
group tblName.FieldName by "Example" into group1
For the second part I needed to perform a Group By on the Sum of another Group By. Below is an example of declaring two new values generated from the Sum of a Group By:
group new { tblName.FieldName1, tblName.FieldName2} by tblName into group1
group new { SumField1 = g.Sum (x => x.FieldName1), SumField2 = g.Sum (x => x.FieldName2) } by "Totals" into newgroup2
Here is a full example of the code I ended up writing, It works well in LINQPad but I am still working on implementation into a Visual Studio C# DataGridView.
from so in TblServiceOrders
join sologs in TblSOLogs on so.SONumber equals sologs.SONumber
where so.DateClosed >= new DateTime(2014,01,17)
where so.DateClosed <= new DateTime(2014,01,17)
where sologs.ElapsedHours != 0 || sologs.ElapsedMinutes != 0
group new {sologs.ElapsedHours, sologs.ElapsedMinutes} by sologs.SONumber into g
group new {hours = g.Sum (x => x.ElapsedHours), mins = g.Sum (x => x.ElapsedMinutes)} by "Totals" into t
select new {
Average = t.Average (x => (x.hours * 60) + x.mins),
Count = t.Count ()
}

Related

EntityFramework - Group by unordered pair in LINQ query

I have a table that contains 4 columns **(User_from, User_to,Message,Date)**
I need to get the last message a user have in all participant chats.
var all = from m in db.Peers
where m.User_Id_To == id || m.User_Id_From == id
group m by new { m.User_Id_To, m.User_Id_From } into g
select g.OrderByDescending(x => x.Date).FirstOrDefault();
The code above works to get the last message for each id,
but still having an issue.
Consider having this 2 records below :
User_from User_To Content Date
1 2 hi 01-01-2018
2 1 wlc 02-02-2018
When I am trying to use my query I am getting both records. I just need the last one according to date.
In GroupBy you can create a conditional key so when you want to group by a pair of values where order dosen't matter you can construct key as an anonymous type with the first property set to min value and the second to max value.
var all = from m in db.Peers
where m.User_Id_To == id || m.User_Id_From == id
group m by new
{
Min = m.User_Id_To < m.User_Id_From ? m.User_Id_To : m.User_Id_From,
Max = m.User_Id_To > m.User_Id_From ? m.User_Id_To : m.User_Id_From
}
into g
select g.OrderByDescending(x => x.Date).FirstOrDefault();

LINQ left join with count - multiple table joins Visual Studio 2015 MVC 5 C#

I'm creating a production dashboard to shows the number of widgets produced by each work center in intervals of one hour. There are 'trigger' and 'target' values for widget production over the course of each hour. My query works...however it doesn't show a particular interval hour if no widgets were produced in that hour. I'm trying to do a sort of left outer join on the interval target table to capture all possible intervals but haven't been able to show intervals that have a zero widget count.
dbvm.widgetSummary = from wc in db.workCenters
from w in db.widgets.Where(w => w.createdByID == wc.ID && w.packDate >= beginDate && w.packDate <= endDate)
from d in db.departments.Where(d => d.ID == wc.deptID && wc.display == true)
from g in db.goals.Where(g => g.workCenterID == wc.ID && g.hour == w.packDate.Value.Hour)
group g by new { wc.ID, wc.friendlyName, g.hour, g.trigger, g.target, d.dept } into n
select new WidgetSummary()
{
createdByID = n.Key.ID,
friendlyName = n.Key.friendlyName,
hour = n.Key.hour,
actualWidgets = n.Count(),
triggerWidgets = n.Key.trigger,
targetWidgets = n.Key.target,
variance = (n.Count() - n.Key.target),
dept = n.Key.dept
};
return View(dbvm);
Well you can do a left outer join.
from wc in db.workCenters
join jw in db.widgets.Where(w => w.packDate >= beginDate && w.packDate <= endDate) on wc.ID equals jw.createdByID into wgj
from w in wgj.DefaultIfEmpty() ...
But why don't you have wc.Widgets and wc.Departments and wc.Goals. Seams to me your domain model is not all that Object oriented and you are just using flat tables without relationships.
Also if you are using EF you might want to look in to perf optimization

LINQ join, group by, sum

I'm creating a safety tracking HR app, and I'm trying to use LINQ to return the lost hours for each month per safety incident type. The relevant entity/table columns are:
[safety_incident]:
[incident_id]
[incident_date]
[incident_type]
[facility_id]
[safety_hours]:
[safety_hours_id]
[incident_id]
[safety_hours_date]
[lost_hours]
The relationship between safety_incident and safety_hours is 0..n, with safety_hours noting hours lost for an incident on specific dates. I'm trying to return a record/object for each combination of incident type and month, over a year (not necessarily all in the same calendar year), where the lost hours is greater than 0. Without the one-year boundaries or limits on facility, I get what I need from SQL with this:
SELECT (datepart(year,safety_hours_date) * 100 + datepart(month,safety_hours_date)),
inc.incident_type, sum(sh.lost_hours)
FROM [hr].[safety_hours] sh
INNER JOIN [hr].safety_incident inc ON sh.incident_id = inc.incident_id
GROUP BY (datepart(year,safety_hours_date) * 100 + datepart(month,safety_hours_date)),
inc.incident_type
HAVING sum(sh.lost_hours) > 0
ORDER BY (datepart(year,safety_hours_date) * 100 + datepart(month,safety_hours_date)),
inc.incident_type
The closest I can get to an accepted LINQ query is this:
var monthInjuryHours =
from sh in _context.safety_hours
where sh.safety_hours_date >= firstMonth && sh.safety_hours_date < monthAfterLast && sh.lost_hours > 0
join inc in _context.safety_incident on sh.incident_id equals (inc.incident_id) into most
from m in most
where (facID == 0 || m.facility_id == facID)
group m by new
{
m.incident_type,
month = new DateTime(sh.safety_hours_date.Year, sh.safety_hours_date.Month, 1)
} into g
select new
{
injury = g.Key.incident_type,
month = g.Key.month,
hours = g.Sum(h => h.lost_hours) // ERROR, can't access lost_hours
};
Unfortunately, I can't specify lost_hours in the "hours" line, or any properties of safety_hours, only those of safety_incident come up after "h.". Very grateful for any help, thanks...
EDIT: I was able to rearrange the query to switch the order of the two tables and got something that ran:
var monthInjuryHours =
from inc in _context.safety_incident
where (facID == 0 || inc.facility_id == facID) && inc.incident_date < monthAfterLast
join sh in _context.safety_hours on inc.incident_id equals (sh.incident_id) into most
from m in most
where m.safety_hours_date >= firstMonth && m.safety_hours_date < monthAfterLast
&& m.lost_hours > 0
group m by new
{
// Note new aliases
inc.incident_type,
month = new DateTime(m.safety_hours_date.Year, m.safety_hours_date.Month, 1)
} into g
select new
{
injury = g.Key.incident_type,
month = g.Key.month,
hours = g.Sum(h => h.lost_hours)
};
But when I tried iterating through it with a foreach, a NotSupportedException pointing to the "in" keyword told me "Only parameterless constructors and initializers are supported in LINQ to Entities." I swear I've had other queries that returned collections of anonymous types but whatever...
Well, for now, the answer is "F(orget) LINQ". Thanks to Yuck's inspiring answer to this topic, a ToList() after the Where() allowed this syntax to get exactly the results I was after:
var monthInjuryHours = _context.safety_incident.Join(_context.safety_hours,
inc => inc.incident_id, sh => sh.incident_id, (inc, sh) => new { Inc = inc, Hours = sh })
.Where(j => j.Hours.lost_hours > 0 && (facID == 0 || j.Inc.facility_id == facID)
&& j.Inc.incident_date < monthAfterLast
&& j.Hours.safety_hours_date >= firstMonth
&& j.Hours.safety_hours_date < monthAfterLast).ToList() //Fixes things
.GroupBy(x => new { Month = new DateTime(x.Hours.safety_hours_date.Year,
x.Hours.safety_hours_date.Month, 1), x.Inc.incident_type })
.Select(g => new { g.Key.Month, g.Key.incident_type,
LostHours = g.Sum(x => x.Hours.lost_hours) })
.OrderBy(h => h.Month).ThenBy(h => h.incident_type);
I know this wasn't the prettiest or simplest example, but I hope it helps someone.

Nested conditional operator in a LINQ query

Using VS 2013 (not C# 6.0 yet)
I have the following LINQ which works:
var radData = (from stop in dbContext.stop_details
join del in dbContext.stop_event on stop.id equals del.stop_id into Inners
from sd in Inners.DefaultIfEmpty()
where stop.ship_date == startDate && stop.cust_ref_5_terminalID == "HEND"
select new
{
shipDate = stop.ship_date,
custRef = stop.cust_ref_5_terminalID,
name = stop.customer.customer_name,
ontime = (int?)sd.ontime_performance,
OTP = ((int?)sd.ontime_performance) < 1 ? "Ontime" : "Late"
}).ToList();
But the value of OTP needs to be the following depending on ontime_performance:
Null - "open"
<1 "Ontime"
1 "One Day Late"
2 "Two Days Late"
'>2 "Three or more days late"
Is there a way to nest this? Nothing I have tried so far works..
Thank you.
You can chain many ?: as follows:
var radData = (from stop in dbContext.stop_details
join del in dbContext.stop_event on stop.id equals del.stop_id into Inners
from sd in Inners.DefaultIfEmpty()
where stop.ship_date == startDate &&
stop.cust_ref_5_terminalID == "HEND"
let value = ((int?)sd.ontime_performance)
select new
{
shipDate = stop.ship_date,
custRef = stop.cust_ref_5_terminalID,
name = stop.customer.customer_name,
ontime = (int?)sd.ontime_performance,
OTP = value == null ? "Open" :
value < 1 ? "On time" :
value == 1 ? "One Day Late" :
value == 2 ? "Two Days Late" : "Three or more days late"
}).ToList();
Also you can store the field in a variable so you don't need to cast it each time: let value = ((int?)sd.ontime_performance)
BTW - the field being int? you can change the value < 1 to value == 0. Consistent with the other conditions and less confusing

Entity Framework DateTIme Query

I'm having an issue with a linq subquery return invalid data when adding in datetime checks as part of the where clause.
This is the original query and it is returning 0; because the result set is null
var subquery =
(from item in g
from e in item.Entry
where e.Type == 1
&& e.EntryType == 2
&& item.StartDate >= priorMonthStartOfDay
&& item.EndDate <= startOfDayQueryParam
select e.Amount).Sum() ?? 0M;
I modified the query to see what the data was; here is that query and the resulting dataset.
var subquery =
(from item in g
from e in item.Entry
where e.Type == 1
&& e.EntryType == 2
select new
{
Amount = e.Amount,
SD = item.StartDate,
ED = item.EndDate,
QD = priorMonthStartOfDay
};
So then I added in the start date comparison and the results are below. The priorMonthStartOfDay is a DateTime with a value of 12/1/2015 12:00:00 AM
var subquery =
(from item in g
from e in item.Entry
where e.Type == 1
&& e.EntryType == 2
&& item.StartDate >= priorMonthStartOfDay
select new
{
Amount = e.Amount,
SD = item.StartDate,
ED = item.EndDate,
QD = priorMonthStartOfDay
};
Why is the date comparison not behaving as I would expected? Given the value of priorMonthStartOfDay, I would expect the result set to be the same for the last two queries. I'm guessing it has something to do with the time equal comparison because if I subtract a second from the priorMonthStartOfDay then the result sets match up again.
The only logical explanation could be that your priorMonthStartOfDay and/or startOfDayQueryParam variables contain time part not shown in the debugger. Note that by default milliseconds part is not shown, not to mention ticks.
To be 100% sure you are comparing against dates, change the date part of the criteria to
&& item.StartDate >= priorMonthStartOfDay.Date
&& item.EndDate <= startOfDayQueryParam.Date

Categories