Entity Framework DateTIme Query - c#

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

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.

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

LINQ How to Show Average of all results in GroupBy

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 ()
}

Casting String as DateTime in LINQ

I have a table with a following format.
PID ID Label Value
------------------------------------------
1 1 First Name Jenna
1 2 DOB 10/12/1980
I need to retrieve all PIDs where First name starting with J and Month of DOB is 10.
in my code, I retrieve these in DataTable in C# and then tried to use LINQ to retrieve the results I want. This is just an example. These Labels could be anything user defines.
using LINQ I am able to retrieve all PIDs where First Name start with J, but every time I tried to Cast Value for DOB I get cast not valid error. I cannot change the column type in the database since Value could contain any type of information.
Here's a piece of my code. I am new to LINQ, and still trying to figure out around it.
var resultQuery = from r in query.AsEnumerable()
where (r.Field<string>("Label") == Label &&
r.Field<DateTime>("Value").Month == 10)
select r.Field<int>("PID");
Since not all items in the Value column of the table are convertible to DateTime, what you have will fail on invalid conversions. You can add in a clause that first checks that the value is a DateTime and only if it is, converts it and checks the .Month property.
DateTime d;
var resultQuery = from r in query.AsEnumerable()
where (r.Field<string>("Label") == Label &&
DateTime.TryParse(r.Field<string>("Value"), out d) &&
d.Month == 10)
select r.Field<int>("PID");
To potentially improve readability, you could also extract this out into a separate method:
var resultQuery = from r in query.AsEnumerable()
let d = TryGetDate(r.Field<string>("Value"))
where (r.Field<string>("Label") == Label &&
d != null &&
d.Month == 10)
select r.Field<int>("PID");
private DateTime? TryGetDate(string value)
{
DateTime d;
return DateTime.TryParse(value, out d) ? d : default(DateTime?);
}
You are going to end up filtering in memory which isn't very efficient.
So first select your data
var data= from r in query.AsEnumerable();
Then filter on the data
var filtered = from item in data
where item.Label == "Label"
&& Convert.ToDateTime(item.DOB).Month == 10
select item.PID;

The group by operation contains an expression that cannot be translated

It compiles normal but when I try to iterate through result of the LINQ query I 've got such exception The group by operation contains an expression that cannot be translated
The query is
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(i => i.value)
};
What expression in group by function is wrong?
Try this:
var query0 = from c in dc.Prices
let date = Convert.ToDateTime(c.data)
where date.CompareTo(left) >= 0 && date.CompareTo(right) <= 0 && c.idsticker.Equals(x)
group c by new { date.Year, date.Month } into groupMonthAvg
select new
{
years = groupMonthAvg.Key.Year,
months = groupMonthAvg.Key.Month,
prices = groupMonthAvg.Average(i => i.value)
};
I'm guessing this is because the data column in Price is a String or some other type other than DateTime.
Try changing the (DateTime)c.data) cast to a Convert.ToDateTime(c.data) instead.
I am not sure if the Linq-SQL translator supports casts.
If possible change the underlying data type in the database to a DateTime if it stores a Date Time value.

Categories