Group and Aggregate with LINQ - c#

I have a timesheet entries as below
ActivityCode : Duration
Project A: 12h:31mins
Project B: 00h:10mins
Project A: 01h:10mins
Project A: 12h:31mins
Project C: 12h:31mins
Project B: 00h:10mins
Project B: 01h:10mins
Project A: 12h:31mins
What is the correct way to group the projects and aggregate their total time spent? i'm trying the below
var summary = from entry in DbSet
where entry.Timesheet.UserID == userid &&
entry.Timesheet.DateSubmitted >= startdate &&
entry.Timesheet.DateSubmitted <= enddate
group entry by entry.ActivityCode.ActivityCode1
into groupEntry
select new TimeSheetSummary()
{
ActivityCode = groupEntry.Key,
HourSpent = Convert.ToInt32(groupEntry.Sum(x => x.Duration)),
Percentage = (Convert.ToInt32(groupEntry.Sum(x => x.Duration)) / 8) * 100,
MinuteSpent = Convert.ToInt32(groupEntry.Sum(x => x.Duration)) * 60,
};

If you want to get a percentage, get a count of the number of activites and use that to divide by.
Not sure if you will need a divide by 0 check here. I'm not sure what the second LINQ will do if there's no data to begin with. Not sure if that would raise an error or not.
Int32 numberOfProjects = (from entry in DbSet
where entry.Timesheet.UserID == userid &&
entry.Timesheet.DateSubmitted >= startdate &&
entry.Timesheet.DateSubmitted <= enddate
select entry.ActivityCode.ActivityCode1).Distinct().Count();
var summary = from entry in DbSet
where entry.Timesheet.UserID == userid &&
entry.Timesheet.DateSubmitted >= startdate &&
entry.Timesheet.DateSubmitted <= enddate
group entry by entry.ActivityCode.ActivityCode1
into groupEntry
select new TimeSheetSummary()
{
ActivityCode = groupEntry.Key,
HourSpent = Convert.ToInt32(groupEntry.Sum(x => x.Duration)),
Percentage = (Convert.ToInt32(groupEntry.Sum(x => x.Duration)) / numberOfProjects) * 100,
MinuteSpent = Convert.ToInt32(groupEntry.Sum(x => x.Duration)) * 60,
};

Here's what i found as answer based on the inputs by others in the thread. thanks!
var groupEntries = from entry in DbSet
where entry.Timesheet.UserID == userId &&
entry.Timesheet.TimeSheetDate <= endDate.Date &&
entry.Timesheet.TimeSheetDate >= startDate.Date
group entry by entry.ActivityCode
into groupEntry
select new
{
ActivityCode = groupEntry.Key,
Duration = Convert.ToInt16(groupEntry.Sum(r => r.Duration))
};
var totalDuration = groupEntries.Sum(r => r.Duration);
var result = from groupEntry in groupEntries
select new TimeSheetSummary()
{
ActivityCode = groupEntry.ActivityCode,
HourSpent = groupEntry.Duration / 60,
MinuteSpent = groupEntry.Duration % 60,
Percentage = groupEntry.Duration / totalDuration * 100
};

Related

Entity framework filtering data by time

I am so sorry from the question, but I can not take a period from a DateTime. for exemple: If I have date "10.10.2016 7:00", 10.10.2016 10:00", I need to take only the rows with the time between "6:00" and "8:00". Next is my code by return an error : "can not use TimeOfDay ",help me please
ds.TrafficJamMorning = (from row in orderQuery
where row.AcceptedTime.TimeOfDay >= new TimeSpan(6, 30, 0) &&
row.AcceptedTime.TimeOfDay <= new TimeSpan(9, 30, 0)
group row by row.AcceptedTime.Date
into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
TimeOfDay Is not supported by the linq provider and it does not know how to parse it into sql. Use instead DbFunctions.CreateTime:
Also instantiate the timespans before the linq query so you do not instantiate a new object every time
var startTime = new TimeSpan(6, 30, 0);
var endTime = new TimeSpan(9, 30, 0);
var result = (from row in orderQuery
let time = DbFunctions.CreateTime(row.AcceptedTime.Hour, row.AcceptedTime.Minute, row.AcceptedTime.Second)
where time >= startTime &&
time <= endTime
group row by DbFunctions.TruncateTime(row.AcceptedTime) into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
Looking again at the question - If all you want to check is that it is between 2 hours then use the Hour property (This won't be nice to write if you want to check for example Hour and Minues and in that case I'd go for my first suggestion):
var result = (from row in orderQuery
where row.AcceptedTime.Hour >= 6
row.AcceptedTime.Hour < 8
group row by DbFunctions.TruncateTime(row.AcceptedTime) into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
I use the following where clause on my IQueryable:
var query = dbContext.GetAllItems().AsQueryable();
//... other filters
if(MusBeBetween6and8){
query = query.Where(item => item.AcceptedTime.Hour > 6 && item.AcceptedTime.Hour < 8);
}
//... other filters
return query.ToList();
Hope it helps. This also works for Oracle + Odac.
ds.TrafficJamMorning = (from row in orderQuery
where
DbFunctions.DiffMinutes( DbFunctions.TruncateTime(row.AcceptedTime), row.AcceptedTime) >= 6 * 60 + 30 &&
DbFunctions.DiffMinutes( DbFunctions.TruncateTime(row.AcceptedTime), row.AcceptedTime) <= 9 * 60 + 30
group row by DbFunctions.TruncateTime(row.AcceptedTime)
into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
I had a similar problem.
You can compare the date parts instead.
where row.Year > s.Year && r.Month > s.Month && row.Day > s.Day

No data return when performing a self join

I have a table contain the payment records of agencies. I want to sum total payment of each agency into 2 columns, first is current day payment and second is the day before payment.
So I try the SQL like this.
select p1.UserName, p1.PaymentAmount, p2.PaymentAmount
from vw_Agency_Payment p1
join vw_Agency_Payment p2 on p1.UserName=p2.UserName
where p1.PaymentDate = '2014-08-07'
and p2.PaymentDate = '2014-08-08'
It is successful and return the data.
But when I convert it to Linq like below:
var yesterday = DateTime.Today.AddDays(-1);
var tomorrow = DateTime.Today.AddDays(1);
var agencyPayment = from y in db2.vw_Agency_Payment
join t in db2.vw_Agency_Payment on y.UserName equals t.UserName
where y.PaymentDate >= yesterday
&& y.PaymentDate < DateTime.Today
&& t.PaymentDate >= DateTime.Today
&& t.PaymentDate < tomorrow
select new AgencyPaymentModel
{
agencyUserCode = y.UserName,
yesterdayPayment = y.PaymentAmount,
todayPayment = t.PaymentAmount,
growth = (t.PaymentAmount - y.PaymentAmount) / y.PaymentAmount * 100
};
return View(agencyPayment.OrderByDescending(c => c.growth).Take(100).ToList());
It return no data.
I don't know what make it wrong!?
Why not the following code (taking the date part of datetime field)?
var yesterday = DateTime.Today.AddDays(-1);
var agencyPayment = from y in db2.vw_Agency_Payment
join t in db2.vw_Agency_Payment on y.UserName equals t.UserName
where y.PaymentDate.Date = yesterday
&& t.PaymentDate.Date = DateTime.Today
select new AgencyPaymentModel
{
agencyUserCode = y.UserName,
yesterdayPayment = y.PaymentAmount,
todayPayment = t.PaymentAmount,
growth = (t.PaymentAmount - y.PaymentAmount) / y.PaymentAmount * 100
};
return View(agencyPayment.OrderByDescending(c => c.growth).Take(100).ToList());
where y.PaymentDate >= yesterday
&& y.PaymentDate < DateTime.Today
&& t.PaymentDate >= DateTime.Today
&& t.PaymentDate < tomorrow
No result will satisfy this condition:
from line 1-2, PaymentDate is limited to yesterday... intersect with line 3 will narrow down to nothing.
Basically you need to draw a reasonable range.
Plus, snippet 2 contains more logic than snippet 1, you should test them under same conditions.

Transforming T-SQL Query into C# LINQ with joins on multiple conditions and also grouping on multiple conditions

First I want to say hello, I'm new to this site ;-)
My problem is to transform the following sql-query into a c# linq-query.
( I HAVE searched hard for an existing answer but I'm not able to combine the solution for
the joins on multiple conditions and the grouping / counting ! )
The sql-query :
DECLARE #datestart AS DATETIME
DECLARE #dateend AS DATETIME
SET #datestart = '01.04.2014'
SET #dateend = '30.04.2014'
SELECT md1.value AS [controller],md2.value AS [action], COUNT(md2.value) AS [accesscount], MAX(re.TIMESTAMP) AS [lastaccess] FROM recorderentries AS re
INNER JOIN messagedataentries AS md1 ON re.ID = md1.recorderentry_id AND md1.position = 0
INNER JOIN messagedataentries AS md2 ON re.ID = md2.recorderentry_id AND md2.position = 1
WHERE re.TIMESTAMP >= #datestart AND re.TIMESTAMP <= #dateend
AND re.messageid IN ('ID-01','ID-02' )
GROUP BY md1.value,md2.value
ORDER BY [accesscount] DESC
Any suggestions are welcome ...
What i have so far is this :
var _RecorderActionCalls = (from r in _DBContext.RecorderEntries
join m1 in _DBContext.MessageDataEntries on
new {
a = r.ID,
b = 0
} equals new {
a = m1.ID,
b = m1.Position
}
join m2 in _DBContext.MessageDataEntries on
new {
a = r.ID,
b = 0
} equals new {
a = m2.ID,
b = m2.Position
}
where r.TimeStamp >= StartDate & r.TimeStamp <= EndDate & (r.MessageID == "VAREC_100_01" | r.MessageID == "VAAUTH-100.01")
group r by new { md1 = m1.Value, md2 = m2.Value } into r1
select new { controller = r1.Key.md1, action = r1.Key.md2, count = r1.Key.md2.Count() }).ToList();
But this throws an exception ( translated from german ) :
DbExpressionBinding requires an input expression with a Listing Result Type ...
UPDATE : Back with headache ... ;-)
I found a solution to my problem :
var _RecorderActionCalls = _DBContext.RecorderEntries
.Where(r => r.TimeStamp >= StartDate & r.TimeStamp <= EndDate & (r.MessageID == "VAREC_100_01" | r.MessageID == "VAAUTH-100.01"))
.GroupBy(g => new { key1 = g.MessageData.FirstOrDefault(md1 => md1.Position == 0).Value, key2 = g.MessageData.FirstOrDefault(md2 => md2.Position == 1).Value })
.Select(s => new {
ControllerAction = s.Key.key1 + " - " + s.Key.key2,
Value = s.Count(),
Last = s.Max(d => d.TimeStamp)
}).ToList();
With this syntax it works for me. Thank you for thinking for me :-)
Something like that:
List<string> messageIdList = new List<string> { "ID-01", "ID-02" };
from re in RecorderEntries
from md1 in MessageDataEntries
from md2 in MessageDataEntries
where re.ID = md1.recorderEntry_id && md1.position == 0
where re.ID = md2.recorderEntry_id && md2.position == 1
where idList.Contains(re.messageid)
let joined = new { re, md1, md2 }
group joined by new { controller = joined.md1.value, action = joined.md2.value } into grouped
select new {
controller = grouped.Key.controller,
action = grouped.Key.action,
accesscount = grouped.Where(x => x.md2.value != null).Count(),
lastaccess = grouped.Max(x => x.re.TimeStamp) }

LINQ query to group records by month within a period

I am looking for some help on adapting the following LINQ query to return all dates within the next 6 months, even those where no records fall within the given month.
var maxDate = DateTime.Now.AddMonths(6);
var orders = (from ord in db.Items
where (ord.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
group ord by new
{
ord.Expiry.Value.Year,
ord.Expiry.Value.Month
}
into g
select new ExpiriesOwnedModel
{
Month = g.Select(n => n.Expiry.Value.Month).First(),
Quantity = g.Count()
}).ToList();
I'd really appreciate any assistance or pointers on how best to implement this.
I'm not sure how well it'll interact with your database, but I'd do this as with a join:
var firstDaysOfMonths = Enumerable.Range(0, 7).Select(i =>
new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i));
var orders = firstDaysOfMonths.GroupJoin(
db.Items,
fd => fd,
ord => new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1),
(fd, ords) => new { Month = fd.Month, Quantity = ords.Count() });
Note you may end up with an extra month where before you didn't (on the first day of the month?)
Stolen from Rawling's answer, if you prefer query syntax for group joins (I do):
var orders =
from month in Enumerable.Range(0, 7)
.Select(i => new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i))
join ord in db.Items
on month equals new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1)
into ords
select new { month.Month, Quantity = ords.Count() };
Alternative if it does not play nice with the database:
var rawGroups = db.Items.Where(item.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
.GroupBy(item => new
{
item.Expiry.Value.Year,
item.Expiry.Value.Month
}, g => new ExpiriesOwnedModel()
{
Month = g.Key.Month,
Quantity = g.Count()
}).ToDictionary(model => model.Month);
var result = Enumerable.Range(DateTime.Now.Month,6)
.Select(i => i > 12 ? i - 12 , i)
.Select(i => rawGroups.Keys.Contains(i) ?
rawGroups[i] :
new ExpiriesOwnedModel()
{ Month = i , Quantity = 0 });

create a query for date and average values

I need to select date and average values from a datacontext's table and I need to group it by year and by month.
In SQL it will look like this
select Year(data) as years, Month(data) as months, Avg(value) as prices from Prices
group by Year(data),MONTH(data)
order by years, months
I've created a LINQ query
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select groupMonthAvg;
But I don't know how to get average values in result
Use the Average function.
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new
{
years = groupMonthAvg.Key.Year,
months = groupMonthAvg.Key.Month,
prices = groupMonthAvg.Average(x=>x.value)
};
Just use the Average function in your select:
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new {
groupMonthAvg.Key.Year,
groupMonthAvg.Key.Month,
YearAverage = groupMonthAvg.Average(x=>x.Year),
MonthAverage = groupMonthAvg.Average(x=>x.Month)
};
This should do it:
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new { Year = groupMonthAvg.Key.Year, Month = groupMonthAvg.Key.Month, Average => groupMonthAvg.Average(i => i.value) };

Categories

Resources