Linq join with last instance in table - c#

I have a workflow table that takes all the steps of a process. Lets work with 2 of those statuses:
Saved (new item saved but not submitted yet)
Submitted (item submitted for review)
Now I want to create a BatchSumbit function that will submit all the unsubmitted items. For this I need to query for all the items which has a latest workflow status of "Saved". All the historical workflow entries for the item still exist and it can go from "Submitted" back to "Saved" a few times.
Here is the table structure:
Now i want a linq query that will give me what I require:
from wasteInformation in wasteDB.WasteInformations
join workFlowHistory in wasteDB.WorkFlowHistories on wasteInformation.WasteInformationId equals workFlowHistory.WasteInformationId
// Join with last instance in workflow table (where workflowHistory.DateAdded is greatest)
where workFlowHistory.WorkFlowStep == "Saved"
&& wasteInformation.WasteProgrammeId == captureModel.WasteProgrammeId
&& wasteInformation.WasteSourceId == captureModel.WasteSourceId
select new
{
WasteInformationId = wasteInformation.WasteInformationId,
FinancialQuarter = wasteInformation.FinancialQuarter,
FinancialYear = wasteInformation.FinancialYear,
WasteProgrammeId = wasteInformation.WasteProgrammeId,
WasteMonth = wasteInformation.WasteMonth,
WasteYear = wasteInformation.WasteYear,
DateCaptured = wasteInformation.DateCaptured,
WasteSourceId = wasteInformation.WasteSourceId,
WasteDate = wasteInformation.WasteDate
}
The query as it is will give be all the saved entries for the item. I want it to give me the item if that item's last entry has a WorkFlowStep of "Saved"
Edit:
I've got something that looks like it works. Still need to test it some more:
var SavedWasteInformation = wasteDB.WasteInformations.Where(wi => wi.WorkFlowHistories.FirstOrDefault(wf => wf.DateAdded == wi.WorkFlowHistories.Max(wf_in => wf_in.DateAdded)).WorkFlowStep == "Saved"
&& wi.WasteProgrammeId == captureModel.WasteProgrammeId
&& wi.WasteSourceId == captureModel.WasteSourceId);
Edit:
My solution above and Vladimirs's below both seem to work, but after inspecting the execution plans Vladimirs's looks like the better option:

Providing that you have collection of WorkFlowHistories on your WasteInformation I believe that query will select WasteInformations with their latest WorkFlowHistory (if any):
from wasteInformation in wasteDB.WasteInformations
where wasteInformation.WasteProgrammeId == captureModel.WasteProgrammeId
&& wasteInformation.WasteSourceId == captureModel.WasteSourceId
select new
{
WasteInformation = wasteInformation,
LastSavedWorkFlowHistory = wasteInformation.WorkFlowHistories
.Where(x => x.WorkFlowStep == "Saved")
.OrderByDescending(x => x.DateAdded)
.FirstOrDefalt()
}

Related

C# LINQ Filter records in child tables

I have a main table "SALES" and two secondary tables "PRODUCTS" and "SERVICES", I need to select only the records in "SALES" that contain some product or service entered by the user, I don't need to bring the sales records and products, just filter. First I made the filter in the table "SALES" by date of sale:
var query = (from p in _contexto.sales
where p.datesale.Value.Date >= Convert.ToDateTime(strDtI).Date &&
p.datesale.Value.Date <= Convert.ToDateTime(strDtF).Date
select p);
Now let's say the user wants to filter also the sales that have products or services with the words in a string Array
words = ['apple', 'beef', 'cleaning', 'haircut']
if you receive the array of words, I tried the filter below, but it didn't work, it kept bringing all the records.
var queryi = (from i in _contexto.products
where words.Contains(i.name) || words.Contains(i.description) select i);
//var queryj = (from i in _contexto.services
//where words.Contains(i.name) || words.Contains(i.description) select i);
//query = query.Where(p => queryi.All(c => c.idsale != p.id) || queryj.All(c => c.idsale != p.id));
query = query.Where(p => queryi.All(c => c.idsale != p.id));
where am I failing, and is there a better and more performant way to do this?
Thank you!
Using more descriptive variable names, and assuming you meant to only find products that have the exact same name or description as one of the words, you would have:
var salesInPeriod = from s in _contexto.sales
where Convert.ToDateTime(strDtI).Date <= s.datesale.Value.Date &&
s.datesale.Value.Date <= Convert.ToDateTime(strDtF).Date
select s;
var matchingidsales = from p in _contexto.products
where words.Contains(p.name) || words.Contains(p.description)
select p.idsale;
var ans = from s in salesInPeriod
where matchingidsales.Contains(s.id)
select s;
PS: I inverted the date comparison since I think it makes it easier to see you are doing a between test.

ASP.NET MVC Return the latest entry in table for the logged in user

I am trying to get the latest record for the logged in employee in my HolidayRequestForm table.
However based on advice from LINQ To Entities does not recognize the method Last. Really? I want to orderbydescending and select the first.
I've tried adding in orderbydescending but I get an error
"Error 3 'System.Data.TypedTableBaseExtensions.OrderByDescending(System.Data.TypedTableBase, System.Func, System.Collections.Generic.IComparer)' is a 'method', which is not valid in the given context
"
Do I have it in the wrong place?
var SD = (from c in db.HolidayRequestForms.OrderByDescending
where (c.Employee.Email == name) && (c.Employee.EmployeeID == c.EmployeeID)
select c.StartDate);
DateTime StartDate = SD.LastOrDefault();
I would like StartDate to give the latest result in the HolidayRequestForm table for the current logged in employee
db.HolidayRequestForms.OrderByDescending
doesn't make sense for two reasons.
It is a method, which needs to be invoked (i.e. have () after it)
You need to tell it what to order by
I'd suggest this as a replacement:
var SD = (from c in db.HolidayRequestForms where (c.Employee.Email == name) && (c.Employee.EmployeeID == c.EmployeeID)
select c.StartDate).OrderByDescending(z => z);
or:
var SD = db.HolidayRequestForms
.Where(c => c.Employee.Email == name && c.Employee.EmployeeID == c.EmployeeID)
.OrderByDescending(z => z.StartDate)
.Select(y => y.StartDate);
You will also want to use FirstOrDefault rather than LastOrDefault.

Filter the results of a query using conditions in asp.net with entity framework

I want to know how many people are in a especify local, using entry and exit validation of this person. If he has entered in the local, then he will generate a transaction with a TrCode = "C0". when he comes out of the place, he will generate a TrCode = "CI". There are other types of TrCode, but it is useless for this kind o validation. i have a query that returns to me this result down bellow:
var query = from a in context.CardDB
join c in context.tblTransaction on a.CardNo equals c.CardNo
where c.TrCode == "C0" || c.TrCode == "CI"
where c.TrSiteCode == sitecode
select c;
Now I have all the rows that have the TrCode == "C0" or TrCode == "CI". But the result gives me all the transactions that the employers(CardDB) did. So the result gives a lot of transactions made of different employers. Sometimes some employer make 2 or even 3 transcations like, when he arrives and when he goes out for lunch, then he cames back etc.
I have to show in a grid just the employers that have in general count more transactions TrCode == "C0" than TrCode == "CI". So, what i have to do to count the transcations of only the employers with the same ID, and, when showing it in the grid, show just a row of this employers and not all the rows.
Since already, Thank you!
var queryNumberC0 = query.Where(c => c.TrCode == "C0").Select(c => c.ID).GroupBy(c => c.Value).ToList();
var queryNumberC1 = query.Where(c => c.TrCode == "C1").Select(c => c.ID).GroupBy(c => c.Value).ToList();
And to get the number of the ID y (Key is the ID of the customer):
int y = [Customer's ID];
int temp = queryNumberC0.Find(c => c.Key == y);
You don't have to specifie the ToList() at the end of the query, do what you want with the result. I just find it easier to browse

How to factorize a Linq request with multiple includes?

My model has:
Several DeviceStatus attached to one mandatory Device
SeveralDevice attached to one mandatory Panel
When I query DeviceStatus, I need to have Device and Panel attached to it in the query result.
... DeviceStatus.Device is null in the query result.
Here is the Linq Query:
using (var actiContext = new ActigraphyContext())
{
var todayStatus =
from s in actiContext.DeviceStatus.Include(s1 => s1.Device.Panel)
where DbFunctions.TruncateTime(s.TimeStamp) == DbFunctions.TruncateTime( DateTimeOffset.Now)
&& s.Device.Panel.Mac == mac
&& (s.Device.Ty == 4 || s.Device.Ty == 9)
select s;
// var tempList = todayStatus.toList();
var todayLastStatus =
from s in todayStatus.Include(s1 => s1.Device.Panel)
let lastTimeStamp = todayStatus.Max(s1 => s1.TimeStamp)
where s.TimeStamp == lastTimeStamp
select s;
var requestResult = todayLastStatus.FirstOrDefault();
return requestResult;
}
If I uncomment the line // var tempList = todayStatus.toList();, where tempList is not used, it works: requestResult.Device is set!
But the bad side is todayStatus.toList triggers a request that brings a huge amount of data.
So how to get the DeviceStatus with its relative objects ?
Note: the database behind is SQL Server 2012
When you call an Include() over a LINQ query, it performs Eagerly Loading.
As documented in MSDN:
Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query. Eager loading is achieved by use of the Include method.
When the entity is read, related data is retrieved along with it. This typically results in a single join query that retrieves all of the data that's needed. You specify eager loading by using the Include method.
So you need to call the .toList() to complete the query execution.
Since the data is huge, you can pickup relative specific columns as per your requirement by using the Select clause.
var todayStatus =
from s in actiContext.DeviceStatus
.Include(s1 => s1.Device.Panel.Select(d => new
{
d.DeviceId,
d.DeviceName,
d.PanelID
}))
where DbFunctions.TruncateTime(s.TimeStamp) == DbFunctions.TruncateTime( DateTimeOffset.Now)
&& s.Device.Panel.Mac == mac
&& (s.Device.Ty == 4 || s.Device.Ty == 9)
select s;
var tempList = todayStatus.toList();
The query doesn't actually run until you do a call like ToList(), which is why uncommenting that line works. If the query is bringing back too much data, then you need to change the query to narrow down the amount of data you're bringing back.
Ok this request is a more simple way to achieve this:
using (var actiContext = new ActigraphyContext())
{
var todayLastStatus =
from s in actiContext.DeviceStatus.Include(s1 => s1.Device.Panel)
where DbFunctions.TruncateTime(s.TimeStamp) == DbFunctions.TruncateTime( DateTimeOffset.Now)
&& s.Device.Panel.Mac == mac
&& (s.Device.Ty == 4 || s.Device.Ty == 9)
orderby s.TimeStamp descending
select s;
var requestResult = todayLastStatus.Take(1).FirstOrDefault();
return requestResult;
}
But the question remains: why didn't I get the relative object in my first request ?

Distinct LINQ Statement - Select Group where Entity doesn't exist

I currently have a LINQ statement that returns an IQueryable to be displayed into a Telerik RadGrid. This statement is set to pull Records that match the Period inputted, and also have the "Premium" Column set to true. It then selects the EmployeeID & ProjectID distinctly using the GroupBy property.
These columns are then displayed in the RadGrid, along with a "PremiumCode" column.
Currently my statement works to display ALL of the records that meet the top credentials (Employee Name, Project, Premium Code), but my end Goal is to pull only those Records which DONT already have a "PremiumCode" assigned to the Project for that particular Employee.
public static IQueryable GetEmptyPremiums(string Period)
{
DataContext Data = new DataContext();
var PR = (from c in Data.System_Times
where c.Period == Period && c.Premium == true
orderby c.System_Employee.LName
select c).GroupBy(s => s.EmployeeID & s.ProjectID).Select(x => x.FirstOrDefault());
return PR;
}
Currently it is displaying properly, but every record is being displayed, not just the ones that require a PremiumCode.
Is there a way to re-work my LINQ statement to only include the records that need a PremiumCode?
EDIT:
Jay,
I have tried to modify your solution to fit my needs, but unfortunately with no success. Records in the Premium table are not added until a Premium Code is defined, therefore there will never be a null "PremiumCode".
To describe my end-goal a tad more clearly: I am looking to show the information in a grid like in the image above. The records shown will be the distinct Time records that have the bool value "Premium" checked as true but don't have a PremiumCode record in the Premium Table.
If the checked record has a matching record in the Premium table (EmployeeID, and ProjectID matching) then it already possesses a Premium Code set and will not need to be displayed in the Grid.
If the checked record has no matching record in the Premium table (EmployeeID, and ProjectID not matching) then it requires a PremiumCode and will need to be displayed in the Grid.
I believe this can be achieved with ".Any()" but I am having troubles aligning my Syntax and Logic to make this Grid display properly.
How about:
DataContext Data = new DataContext();
var projectsWithoutPremium = Data.Premiums.Where(p => p.PremiumCode == null)
.Select(p => p.ProjectId);
var PR = (from c in Data.System_Times
where c.Period == Period && c.Premium == true
&& projectsWithoutPremium.Contains(c.ProjectId)
orderby c.System_Employee.LName
select c).GroupBy(s => s.EmployeeID & s.ProjectID).Select(x => x.FirstOrDefault());
return PR;
update in response to question edit
DataContext Data = new DataContext();
var PR = (from c in Data.System_Times
where c.Period == Period && c.Premium == true
&& !Data.Premiums.Any(p => p.ProjectID == c.ProjectID && p.EmployeeID == c.ProjectID)
orderby c.System_Employee.LName select c)
.GroupBy(s => s.EmployeeID & s.ProjectID)
.Select(x => x.FirstOrDefault());
return PR;
If premium code is a string, you might want to try adding something like .Where(x => string.isNullOrEmpty(x.PremiumCode)) before the GroupBy clause.

Categories

Resources