linq: groupby with multiple nullable types - c#

I have the following table:
| AppointID | UserID | AppointSet | AppointAttended | AppointCancelled | AppointRescheduled |
| 1 | 1 | 2/15/2011 | | 3/11/2011 | |
| 2 | 1 | 2/17/2011 | | | 3/11/2011 |
| 3 | 1 | 3/11/2011 | 3/11/2011 | | |
| 4 | 1 | 3/10/2011 | | 3/11/2011 |
|
What I'm trying to do is create the following output that counts the activity by day.
| Date | Set | Attended | Rescheduled | Cancelled |
| 3/10/2011 | 1 | | | |
| 3/11/2011 | 1 | 1 | 1 | 2 |
Note that I've defined the fields AppointAttended, AppointCancelled and AppointRescheduled as nullable because there might not be a date for these.
This is what I have so far and I'm struggling with the groupby because I need to group by multiple columns, and it's a nullable type, and I can't get the .Key to work! In other words, I feel really stuck with this:
var OutputMonthlyActivity = from appnt in MyDC.LeadsAppointments
where appnt.UserID == TheUserID
where (appnt.AppointSet.Month == TheDate.Month && appnt.AppointSet.Year == TheDate.Year) ||
(appnt.AppointAttended.Value.Month == TheDate.Month && appnt.AppointAttended.Value.Year == TheDate.Year) ||
(appnt.AppointRescheduled.Value.Month == TheDate.Month && appnt.AppointRescheduled.Value.Year == TheDate.Year) ||
(appnt.AppointCancelled.Value.Month == TheDate.Month && appnt.AppointCancelled.Value.Year == TheDate.Year)
group appnt by new { appnt.AppointSet, appnt.AppointRescheduled, appnt.AppointAttended, appnt.AppointCancelled } into daygroups
where daygroups.Count() > 1
select new ViewMonthlyActivityModel()
{
ViewDate = daygroups.Key,
CountTotalSetOnDay = (from c in daygroups
where c.AppointSet.Date == daygroups.Key
select c.AppointID).Count(),
TheDate is a datetime that's passed in as a parameter and that represents the 1st day of the month that's queried: 3/1/2011 for the month of the March for example.
I get "An anonymous type cannot have multiple properties with the same name" in the groupby statement and the .Key doesn't work so the grouping by day is not working.
If you have any suggestions, that'd be really appreciated.
Thanks.

You can try to explicitly set the right names of the properties of you anonymous type on which you group:
group appnt by new {
Set = appnt.AppointSet,
Rescheduled = appnt.AppointRescheduled,
Attended = appnt.AppointAttended,
Cancelled = appnt.AppointCancelled } into daygroups

Related

Entity Framework query - select only record until timestamp from another table

I have 2 tables.
The 1st one is History table - data received by different devices.
+----------+-------------+--------------------+
| DeviceId | Temperature | TimeStamp |
+----------+-------------+--------------------+
| 1 | 31 | 15.08.2020 1:42:00 |
| 2 | 40 | 15.08.2020 1:43:00 |
| 1 | 32 | 15.08.2020 1:44:00 |
| 1 | 34 | 15.08.2020 1:45:00 |
| 1 | 20 | 15.08.2020 1:46:00 |
| 2 | 45 | 15.08.2020 1:47:00 |
+----------+-------------+--------------------+
The 2nd one is DeviceStatusHistory table
+----------+---------+--------------------+
| DeviceId | Status | TimeStamp |
+----------+---------+--------------------+
| 1 | 1(OK) | 15.08.2020 1:42:00 |
| 2 | 1(OK) | 15.08.2020 1:43:00 |
| 1 | 1(OK) | 15.08.2020 1:44:00 |
| 1 | 0(FAIL) | 15.08.2020 1:44:30 |
| 1 | 0(FAIL) | 15.08.2020 1:46:00 |
| 2 | 0(FAIL) | 15.08.2020 1:46:10 |
+----------+---------+--------------------+
Since the device1 starts failing from 15.08.2020 1:44:30, I don't want its record that goes after that timestamp.
The same for the device2.
So as a final result I want to have only data of all devices until they get first FAIL status:
+----------+-------------+--------------------+
| DeviceId | Temperature | TimeStamp |
+----------+-------------+--------------------+
| 1 | 31 | 15.08.2020 1:42:00 |
| 2 | 40 | 15.08.2020 1:43:00 |
| 1 | 32 | 15.08.2020 1:44:00 |
+----------+-------------+--------------------+
I tried something like this
var query = _context
.History
.Include(h => h.Device)
.AsNoTracking()
.Where(h => h.DeviceTimeStamp <= h.Device.DeviceStatusHistory.FirstOrDefault(st => st.Status == 0).TimeStamp);
The problems is if a device never fails, I don't get its history at all.
Basically you need "not exists" condition, i.e. get all History records for which does not exist DeviceStatusHistory record with the same DeviceId, Status == 0 and greater Timestamp.
Which with navigation properties in LINQ is expressed with !Any(...) condition rather than FirstOrDefault():
.Where(h => !h.Device.DeviceStatusHistory.Any(st =>
st.Status == 0 && st.TimeStamp > h.DeviceTimeStamp));
I have some untested code, can you try it. I think the logic should work for you.
var query = _context
.History
.Include(h => h.Device)
.AsNoTracking()
.Where(
h => h.DeviceTimeStamp <= h.Device.DeviceStatusHistory.FirstOrDefault(st => st.Status == 0)?.TimeStamp ?? DateTime.MaxValue);
Below SQL query works for your needs
select * from History h
where h.TimeStamp < coalesce(
(
select min(TimeStamp) from DeviceStatusHistory where Status = 0 and DeviceId = h.DeviceId
) ,Curdate()
)
If we try to translate it to linq it will be like this :
.Where(h => h.DeviceTimeStamp < (h.Device.DeviceStatusHistory
.Where(st => st.Status == 0)
.Min(st=> st.TimeStamp) ?? DateTime.Now))
SQL Fiddle

Check if id's in a comma-separated string match any id's in another string array

I have two tables in DB Master Setup Approval and Order Details and I want check On any Master Approval Setup, this purchase order will go on relying CostCenter.
Table Master Setup Approval:
|-------|---------|-------------|-------------|---------------|---------------------|
| ID | Name | CRG_COM_ID| CRG_BRN_ID |ApprovalTypeId |CostCenter(string) |
|-------|---------|-------------|-------------|---------------|---------------------|
| 1 | Setup1 | 1 | 1 | 1 | "1,2,5,7" |
|-------|---------|-------------|-------------|---------------|---------------------|
| 2 | Setup2 | 1 | 1 | 1 | "1,3,6" |
|-------|---------|-------------|-------------|---------------|---------------------|
Table OrderDetails :
|-------|---------|-------------|-------------|------------------|
| ID | Name | CRG_COM_ID| CRG_BRN_ID |CostCenterID(long)|
|-------|---------|-------------|-------------|------------------|
| 1 | Item1 | 1 | 1 | 1 |
|-------|---------|-------------|-------------|------------------|
| 2 | Item2 | 1 | 1 | 7 |
|-------|---------|-------------|-------------|------------------|
This is my code:
var orderDetails = db.OrderDetails.Where(c => c.OrderId == orderId);
var costc = orderDetails.Select(c => c.CostCenterId.Value).ToList().ConvertAll<string>(delegate (long i) { return i.ToString(); });
var ApprovalProcess_Count12 = db.MasterSetupApproval.Where(x =>
x.CRG_COM_ID == order.CompanyId &&
(x.CRG_BRN_ID == null || x.CRG_BRN_ID == order.BranchId) &&
x.ApprovalTypeId == (int)ApprovalTypes.PO &&
x.CostCenter.Split(',').Select(aee => aee).Any(val => costc.Contains(val))
).ToList();
I am getting the following error:
LINQ to Entities does not recognize the method 'System.String[] Split(Char[])' method, and this method cannot be translated into a store expression.
Output should be:
|-------|---------|-------------|-------------|---------------|---------------------|
| ID | Name | CRG_COM_ID| CRG_BRN_ID |ApprovalTypeId |CostCenter(string) |
|-------|---------|-------------|-------------|---------------|---------------------|
| 1 | Setup1 | 1 | 1 | 1 | "1,2,5,7" |
|-------|---------|-------------|-------------|---------------|---------------------|
Assuming you are working with a badly-designed DB (as pointed out by Crowcoder) that comma-separated values should not be present in a database, you may refer this to tackle your way through.
HTH!

Querying one record of each type between dates

I have a table (attendance) like this
+----+--------------------------+-------+------------+
| id | employeeId | type | date |
+----+--------------------------+-------+------------+
| 0 | alberto | A | 2019-10-01 |
| 1 | alberto | A | 2019-10-02 |
| 2 | alberto | A | 2019-10-03 |
| 3 | pedro | B | 2019-10-01 |
| 4 | pedro | B | 2019-10-02 |
| 5 | pedro | B | 2019-10-03 |
| 6 | juan | A | 2019-10-01 |
| 7 | juan | A | 2019-10-02 |
| 8 | oscar | A | 2019-10-01 |
| 9 | marcelo | B | 2019-10-01 |
| 10 | marcelo | B | 2019-10-02 |
| 11 | marcelo | B | 2019-10-03 |
+----+--------------------------+-------+------------+
Using a LINQ expression and given a date range for example 2019-10-01 to 2019-10-03, I want to find the first employeeId of each type that has a record on each of the dates between the range meaning one record for 2019-10-01, another for 2019-10-02 and another for 2019-10-03, so the result should be
alberto | typeA
pedro | TypeB
So far I have this:
_context.Attendance
.Where(s => s.Date>= sm.InitialDate && s.Fecha <= sm.FinalDate)
I have tried a few things but I could not make it work.
You have made a good start, after filtering out the elements outside the date range, you should group by the type, then get the first one in each group:
var list = _context.Attendance
.Where(s => s.Date>= sm.InitialDate && s.Fecha <= sm.FinalDate)
.GroupBy(x => x.Type)
.Select(x => x.First())
.Select(x => new { x.EmployeeId, x.Type })
.ToList();

Linq Return List of Objects after max effective From Date

Hiiya,
So trying to work out a query that returns a list of objects based on its max effective from date before a selected datetime.
Principle Example
+----+--------------+------------+----------------+
| id | Employee_Num | Money_Owed | Effective_From |
+----+--------------+------------+----------------+
| 1 | 1 | 10.11 | 20/10/2017 |
+----+--------------+------------+----------------+
| 2 | 1 | 15.11 | 24/10/2017 |
+----+--------------+------------+----------------+
| 3 | 1 | 20.11 | 30/10/2017 |
+----+--------------+------------+----------------+
| 4 | 2 | 6.89 | 20/10/2017 |
+----+--------------+------------+----------------+
| 5 | 2 | 9.89 | 25/10/2017 |
+----+--------------+------------+----------------+
| 6 | 2 | 12.89 | 29/10/2017 |
+----+--------------+------------+----------------+
so say I want to return each employees record as of 21/10/17 I would expect the below to be returned as a list of objects (Entities)
+----+--------------+------------+----------------+
| id | Employee_Num | Money_Owed | Effective_From |
+----+--------------+------------+----------------+
| 1 | 1 | 10.11 | 20/10/2017 |
+----+--------------+------------+----------------+
| 4 | 2 | 6.89 | 20/10/2017 |
+----+--------------+------------+----------------+
Then as of 24/10/2017
+----+--------------+------------+----------------+
| id | Employee_Num | Money_Owed | Effective_From |
+----+--------------+------------+----------------+
| 2 | 1 | 15.11 | 24/10/2017 |
+----+--------------+------------+----------------+
| 4 | 2 | 6.89 | 20/10/2017 |
+----+--------------+------------+----------------+
Im guessing the Query should be something along these lines but cant work out what it should be.
var qry = from t in db.Entity.Where(x => x.Effective_From <= as_of_date)
.OrderBy(x => x.Employee_Num)
select new Entity { *need rest of entity fields* ,effective_from = Max(e => e.Effective_From) };
Any help to finish off or point me in a different direction would be appreciated...
I believe what you'd be looking to do would involve a GroupBy expression to group by employee number, ordered by Employee number and then effective from descending, then take the first available combination.
var qry = db.Entity.Where(x => x.Effective_From <= as_of_date)
.OrderBy(x => x.Employee_Num)
.ThenByDescending(x => X.Effective_From)
.GroupBy(x => x.Employee_Num)
.Select(g => g.FirstOrDefault())
.ToList();
This is if you want the actual entities. I'd avoid doing something like "select new" for an entity as while this will contain the data of the relevant entities, they aren't the same reference as far as the context is concerned. .Select() would be used to retrieve an anonymous type suited for some logic or to populate a view model for a front end, or a DTO for an API result.

Get record for each type with latest / bigger dates in C# DBML

I have a DB table which has some data as follows:
LINE | QTY | USERID | DATE
-----------------------------------------
1 | 5 | qb1 | 2015-03-02 11:23:25
2 | 1 | qb2 | 2015-03-02 18:24:03
3 | 3 | ch1 | 2015-03-03 05:38:49
1 | 2 | qb1 | 2015-03-03 08:47:02
2 | 4 | qb2 | 2015-03-03 14:01:31
3 | 2 | ch1 | 2015-03-03 21:11:53
1 | 4 | qb1 | 2015-03-04 09:34:04
2 | 5 | qb2 | 2015-03-04 15:29:27
3 | 1 | ch1 | 2015-03-04 19:28:33
As you can see I have only 3 unique LINE values in the DB. I require a LINQ query to select the latest record of every line. The date can be any date, I just need the latest status of the lines based on "DATE" field.
At the moment I am doing it very roughly something like this:
var line1 = db.GetTable<lnk_sts>().Where(x=> x.LINE== 1).OrderByDescending(x => x.DATE).FirstOrDefault();
Same for the other 2. What I Require is a list of lnk_sts with only the ones with a bigger date, in this case:
LINE | QTY | USERID | DATE
---------------------------------------
1 | 4 | qb1 | 2015-03-04 09:34:04
2 | 5 | qb2 | 2015-03-04 15:29:27
3 | 1 | ch1 | 2015-03-04 19:28:33
What you need to do is, group on Line and then take the first item in group after ordering in descending.
db.GetTable<lnk_sts>()
.GroupBy(x=>x.LINE)
.Select(x=>x.OrderByDescending(o=>o.DATE).FirstOrDefault())
.ToList();
First group by the line, then order by date and take the last item.
var result = db.GetTable<lnk_sts>().GroupBy(x=> x.LINE).Select(x => x.OrderBy(y => y.Date).Last())

Categories