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

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.

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

EF Linq group by ICollection of objects

All,
I have a Linq query which fetches a list of events which works great. The problem I'm facing is that Events contains a ICollection of Artists called headliners and in the list I only want 1 event per,set of, Artist(s).
The query underneath works fine but: I require a top 10 of Events but only one Event per, set of, artist(s) for sorting the popularity of the artist with highest popularity can be used - not what i want.
Context.Events
.Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified)
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity))
.Take(10)
.ToList();
How can I adjust the query above that I only get one Event per Artist. I would need to do some sort of grouping to see if the event is performed by same (set of) Artist(s).
I'm looking into using the Artist's primary key but because it is an collection i cannot get it to work. I already tried the String.Join to get a single unique key for the headliners. This is however not support in entity framework.
Is this something that can (gracefully) be supported by Linq to EF?
The following SQL query does almost what i want expect that it won't work with multiple artist for the same event
SELECT MAX(E.EventId), MAX(E.Name)
FROM [dbo].[Events] E
INNER JOIN [dbo].[Stages] S ON E.StageId = S.StageId
INNER JOIN [dbo].[Venues] V ON S.VenueId = V.VenueId
INNER JOIN [dbo].[Areas] A ON V.AreaId = A.AreaId
INNER JOIN [dbo].[Headliners] H ON E.EventId = H.EventId
INNER JOIN [dbo].[Artists] A2 ON A2.ArtistId = H.ArtistId
WHERE E.IsVerified = 1 AND E.StartDateTimeUtc>GETDATE() AND A.AreaId = 1
GROUP BY A2.ArtistId, A2.Name, A2.EchoNestHotttnesss
ORDER BY A2.EchoNestHotttnesss desc
Challenging task, but here it is:
var availableEvents = db.MusicEvents.Where(e =>
e.Stage.Venue.AreaId == 1 && e.StartDateTimeUtc > DateTime.UtcNow && e.IsVerified);
var topEvents =
(from e1 in availableEvents
where e1.Headliners.Any() &&
!availableEvents.Any(e2 => e2.StartDateTimeUtc < e1.StartDateTimeUtc &&
!e2.Headliners.Any(a2 => !e1.Headliners.Any(a1 => a1.Id == a2.Id)) &&
!e1.Headliners.Any(a1 => !e2.Headliners.Any(a2 => a2.Id == a1.Id)))
orderby e1.Headliners.Max(a => a.Popularity) descending
select e1)
.Take(10)
.ToList();
The first subquery (availableEvents) is just for reusing the "availability" filter inside the main query. It does not execute separately.
The critical part is the condition
!availableEvents.Any(e2 => e2.StartDateTimeUtc < e1.StartDateTimeUtc &&
!e2.Headliners.Any(a2 => !e1.Headliners.Any(a1 => a1.Id == a2.Id)) &&
!e1.Headliners.Any(a1 => !e2.Headliners.Any(a2 => a2.Id == a1.Id)))
The idea is to exclude the later events for the same set of headliners. It should be read this way:
Exclude the event if there is another available event starting earlier and there is no at least one artist from either event that is not headliner of the other event (i.e. they have the same headliner set).
Edit:
A pretty decent partial-LINQ lazily executed solution could be done in this way:
Firstly, get your query up to the ordered events based on popularity:
var evArtists = Context.Events
.Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified)
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity));
Secondly, since a ICollection<Artist> can be unordered yet forming equal set, creates an intermediate function to check if two ICollection<Artist> are of identical members:
private bool areArtistsEqual(ICollection<Artist> arts1, ICollection<Artist> arts2) {
return arts1.Count == arts2.Count && //have the same amount of artists
arts1.Select(x => x.ArtistId)
.Except(arts2.Select(y => y.ArtistId))
.ToList().Count == 0; //when excepted, returns 0
}
Thirdly, use the above method to get the unique artists set in the query results, put the results in a List, and fill the List with the number of elements you need (say, 10 elements):
List<Events> topEvList = new List<Events>();
foreach (var ev in evArtists) {
if (topEvList.Count == 0 || !topEvList.Any(te => areArtistsEqual(te.Headliners, ev.Headliners)))
topEvList.Add(ev);
if (topEvList.Count >= 10) //you have had enough events
break;
}
Your result is in the topEvList.
Benefits:
The solution above is lazily executed and is also pretty decent in the sense that you can really break down the logic and check your execution piece by piece without breaking the performance.
Note that using the method above you do not need to refer to the evArtists (which is your large query) other than by its individual element ev. Using full-LINQ solution is possible, yet you may need to refer to evArtists.Any to find the duplicates set of artists (as you do have have memory of what sets has been chosen before) from the original ordered query itself (rather than by simply using its element (ev) one by one).
This is possible because you create a temporary memory topEvList which records what sets have been chosen before and only need to check if the next element (ev) is not among the already selected set of artists. Thus, you do not impair your performance by checking you set of artists against the whole ordered query every time.
Original:
You are almost there actually. What you further need are LINQ GroupBy and First, and put your Take(10) the last:
var query = Context.Events
.Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified)
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity))
.GroupBy(a => a.ArtistId)
.Select(e => e.First())
.Take(10);
Since in by this query you have sorted your headliner artist:
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity))
Then you only need to group your headliners by ArtistId:
.GroupBy(a => a.ArtistId)
Thus each artist would be having one group. Then next, you only want the first element in the group (supposedly the most popular Event per Artist):
.Select(e => e.First())
And thus you will get all the most popular events per artist. And lastly, among these most popular events per artist, you only want to take 10 of them, thus:
.Take(10);
And you are done!

Linq join with last instance in table

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

Categories

Resources