LINQ Group By with subquery - c#

I am trying to make a query on LINQ using a GroupBy, where on the Select statement I try to make some kind of subquery. The idea is that I have 7 days to look for, I need to get the average value of these last 7 days but also the current value for the current day. So I have tried the following code:
var oDate = DateTime.UtcNow;
var startDate = oDate.AddWorkingDays(-7);
var endDate = oDate.AddWorkingDays(-1);
var races = RaceTimes.Where(
x =>
x.ODate >= startDate && x.ODate <= oDate && x.Status == "Finished")
.GroupBy(x => x.Athletes.Name).Select(x => new
{
Name = x.Key,
Description = "Just a Random Description for now",
ODate = oDate,
SevenDaysAvg = (int?)x.Where(y => y.ODate >= startDate && y.ODate <= endDate).Average(y => SqlFunctions.DateDiff("s", y.StartTime, y.EndTime)),
RaceTime = (int?)x.Where(y => y.ODate == oDate).Average(y => SqlFunctions.DateDiff("s", y.StartTime, y.EndTime))
}).ToList().Where(x => x.RaceTime!= null).ToList();
The SevenDaysAvg (average) works fine, it returns me the average of the last 7 days races. However, Latency is always returning me null.
I am trying to make the translation of my query from SQL, which works fine:
...
AVG(case when h.ODate BETWEEN '20140219' AND '20140227' then DATEDIFF(SECOND, h.StartTime, h.EndTime) else null end) AS SevenDaysAvg,
AVG(case when h.ODate = '20140227' then DATEDIFF(SECOND, h.StartTime, h.EndTime) else null end) AS RaceTime,
...
So what am I doing wrong in my LINQ query? This what I am doing is not possible? I've also tried another aproach. First calculate all averages, and then in the main query have something like:
SevenDaysAvg = averages.Where(y => y.Name== x.Athletes.Name).Select(y => y.SevenDaysAvg).FirstOrDefault()
Once again, this approach didnt work, it keeps giving me empty results. Whats wrong here?

Related

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.

Can't compare dates using SQLite with EF6

I'm trying to compare dates using Linq to Entities on a SQLite database. The following code works, but I need to trim off the time portion to get the correct results.
return (from c in Context.Car
join d in Context.Driver on c.CarID equals d.DriverID
join r in Context.Rides on c.CarID equals r.RideID into rideJoin
from rides in rideJoin.DefaultIfEmpty()
where c.IsActive && d.IsActive
group rides by new { c.CarID, d.FullName, d.HireDate, d.FirstPayRiseDate } into grp
select new MyCustomClass
{
CarID = grp.Key.CarID,
Driver = grp.Key.FullName,
NumberOfRides = grp.Count(x => x != null && x.RideDate >= grp.Key.HireDate && x.RideDate <= grp.Key.FirstPayRiseDate)
}).OrderBy(x => x.Driver ).ToList();
I've tried using System.Data.Entity.DBFunctions like so and I get this error:
NumberOfRides = grp.Count(x => x != null && DbFunctions.TruncateTime(x.RideDate) >= grp.Key.HireDate && DbFunctions.TruncateTime(x.RideDate) <= grp.Key.FirstPayRiseDate)
SQL logic error or missing database no such function: TruncateTime
I also get the same error with DBFunctions.DiffDays()
I've also tried casting to Date like so and get this error:
NumberOfRides = grp.Count(x => x != null && x.RideDate.Date >= grp.Key.HireDate && x.RideDate.Date <= grp.Key.FirstPayRiseDate)
'Date' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported
What gives? How am I supposed to do Date functions in Linq to Entities with SQLite??
I need to trim off the time portion to get the correct results
No you don't. If you want to include the rows from startDate through endDate inclusive then just use
... && x.RideDate >= startDate && x.RideDate < endDate.AddDays(1)
(Note that the second comparison is now "strictly less than".)
How are you storing dates on the database ? as unix time integrs ?
in that acse you can amend your connection string to include this following config setting and it will make it easy to read the datetime value via EF.
datetimeformat=UnixEpoch;datetimekind=Utc
So something like :
data source="|DataDirectory|\data.sqlite";datetimeformat=UnixEpoch;datetimekind=Utc
Ref: https://stackoverflow.com/a/24323591/3660930

LINQ not returning the right ROW of data

This is my full set of data, and the highlighted row should be returned by my query but its not returning any data.
http://screencast.com/t/S0DgVgIuQS
and the linq is like this
if (filtro.Equals("Pendientes"))
{
var activididadesFiltradas = actividades
.Where(p => (Convert.ToDateTime(p.fechaVencimiento) >= DateTime.Today)
&& (Convert.ToDateTime(p.fechaVencimiento) <= DateTime.Today.AddDays(7))
&& (p.estado != "Documentada")).ToList();
return activididadesFiltradas;
}
basically it should return data where fechavencimiento is between today and 7 days in the future, and estado different to Documentada, if you see the screenshot, the conditions are met.
btw, the dataset has the dates in string, so I have to convert them before comparing
Upodate1
I also tried this but no results
var present = DateTime.Today;
var future = DateTime.Today.AddDays(7);
var validIDs = actividades
.Select(s => new
{
id = s.ID,
filter = s.fechaVencimiento,
date = Convert.ToDateTime(s.fechaVencimiento, CultureInfo.InvariantCulture ),
present = new DateTime(present.Year, present.Month, present.Day),
future = new DateTime(future.Year, future.Month, future.Day)
})
.Where(m => (m.date - m.present).TotalDays >= 0 && (m.future - m.date).TotalDays >= 0 && !m.filter.Equals("Documentada"))
.Select(s => s.id);
var activididadesFiltradas = actividades.Where(a => validIDs.Contains(a.ID)).ToList();
Update 2
This is the record that should be returned:
http://screencast.com/t/1SUjRV2rVGa
`"(Convert.ToDateTime(p.fechaVencimiento) >= DateTime.Today)"`
7/09/2015 is definitely not >= today (in MM/dd/yyyy) format
Your comparisons don't take the time component into account. Even if your date is parsed correctly, if it doesn't contain a time element in the string representation, you might end up in a situation where your date is 2015/01/01 00:00:00:000 and 'today' is 2015/01/01 03:12:56:001. Seeing that you're only interested in days, I would suggest 'normalising' your dates first and then filtering based on the result.
Possibly something like this:
var format = "dd/MM/yyyy";
var present = DateTime.ParseExact (DateTime.Today.ToString(format, CultureInfo.InvariantCulture), format, CultureInfo.InvariantCulture);
var future = DateTime.ParseExact (DateTime.Today.AddDays (7).ToString(format, CultureInfo.InvariantCulture), format, CultureInfo.InvariantCulture);
var activididadesFiltradas = actividades
.Where(p => ((DateTime.ParseExact(p.fechaVencimiento, format, CultureInfo.InvariantCulture) - present).TotalDays >= 0)
&& (future - DateTime.ParseExact(p.fechaVencimiento, format, CultureInfo.InvariantCulture)).TotalDays >= 0
&& (p.estado != "Documentada")).ToList();
return activididadesFiltradas;

Whats wrong in this Linq query

var MyCours = Db.COURS.Where(C => C.CLASSE_ID == ClassID
&& DateTime.Now>= C.START_DATE
&& DateTime.Now <= C.END_DATE)
.ToList();
Some change still dont work !
A likely problem is that the provider can't project DateTime.Compare into a SQL statement. There is potentially also a logical error in the direction of comparison (unless you really want enddate < now < startdate), and I would also suggest using .ToList() to materialize into a list:
var theTimeNow = DateTime.Now;
var MyCours = Db.COURS.Where(C => C.CLASSE_ID == ClassID
&& theTimeNow >= C.START_DATE
&& theTimeNow <= C.END_DATE)
.ToList();
Projecting DateTime.Now into a variable isolates the non-determinism of it, i.e. to ensure that both comparisons are against the same time.

Short Date in Linq

I want my query to stop displaying time and just the date. This is what I've tried to far:
Query= (from z in ctx.Interactions
where z.ActivityDate <= StartDateTo
&& z.ActivityDate >= EndDateTo
&& z.Indepth == false
select new
{
Date = new DateTime(z.ActivityDate.Year, z.ActivityDate.Month, z.ActivityDate.Day),
Subject = z.Subject
}).ToList();
And
Query= (from z in ctx.Interactions
where z.ActivityDate <= StartDateTo
&& z.ActivityDate >= EndDateTo
&& z.Indepth == false
select new
{
Date = z.ActivityDate.Date,
Subject = z.Subject
}).ToList();
And both didn't work.
LINQ to Entities does not recognize the method 'System.String ToString(System.String)' method, and this method cannot be translated into a store expression. when trying to apply a string method.
You can use anyDate.ToString("ddMMyyyy");//any preferred format.
Not sure if that is what you are looking for!
Your queries return objects with Date & Subject properties.
In the Date property you are passing a DateTime object. In order to display the short date you have a "ToShortDateString()" function on a date.
If you dont want to work with a date and prefer selecting a string, then do the conversion inside the linq query.
Use this if you want to return strings:
var q = (from z in ctx.Interactions
where z.ActivityDate <= StartDateTo && z.ActivityDate >= EndDateTo && z.Indepth == false
select new { Date = z.ActivityDate.Date.ToShortDateString(), Subject = z.Subject }).ToList();
You would need to perform the formatting at the time of the binding. As you don't show the actual binding code, it is hard to specifically address your situation but lets look at what happens in your query:
Query= (from z in ctx.Interactions
where z.ActivityDate <= StartDateTo && z.ActivityDate >= EndDateTo && z.Indepth == false
select new { Date = z.ActivityDate.Date, Subject = z.Subject }).ToList();
Once LINQ handles this query, the resulting Query variable should be of type List<DateTime>. The way you have the query working you would return a list of DateTimes in a format like this:
2014-04-23 00:00:00
2014-03-28 00:00:00
etc...
In order to bind this without the time value, you need to call ToString() on each element (or the desired element) of the list at the time of binding.
Assuming you are using a ListBox or something similar you could write the following:
foreach (var date in myList) //this is the resultant list from the query
{
listBox1.Items.Add(date.ToString("MM/dd/yyyy");
}
If you are literally binding to a DataSource property, you will need to convert your List<DateTime> to a List<string> with the formatted values.
ToShortDateString() may help you.
Query= (from z in ctx.Interactions
where z.ActivityDate <= StartDateTo
&& z.ActivityDate >= EndDateTo
&& z.Indepth == false
select new
{
Date = z.ActivityDate.ToShortDateString(),
Subject = z.Subject
}).ToList();
convert date into string like below
string stringDate=string.empty;
stringDate=Convert.ToDateTime("2014-04-23 00:00:00").ToShortDateString();
it will give output like
2014-04-23

Categories