Subqueries in Linq when attempting to get counts - linq

I'm trying to get some subqueries to work in a call of mine. I am trying to make this call one trip to the database but cannot for the life of me solve how. The query breaks on the GoodSections portion. I have tried many different methods of doing this. I keep getting this message:
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()
Can someone help me?
var test = context.UserAssessments.Include(n => n.Assessment).Include(n => n.UserSections).ThenInclude(userSection => userSection.Section)
.OrderBy(n => n.StartDateTime);
MyAssessments = await test.Select(assessment => new MyAssessmentVM()
{
Assessment = assessment.Assessment.Name,
CompletedDateTime = assessment.CompletedDateTime,
StartedDateTime = assessment.StartDateTime,
UserAssessmentID = assessment.ID,
GoodSections = assessment.UserSections.Where(userSection => userSection.Section.SectionType != SectionTypeEnum.Reading)
.Count(n => n.Percentage < n.Section.ReadinessRangeHigh && n.Percentage > n.Section.ReadinessRangeLow)
}).ToListAsync();

Your code:
GoodSections = assessment.UserSections
.Where(userSection => userSection.Section.SectionType != SectionTypeEnum.Reading)
// End of Where!
.Count(n => n.Percentage < n.Section.ReadinessRangeHigh
&& n.Percentage > n.Section.ReadinessRangeLow)
Apparently, every Assesment has a sequence of zero or more UserSections. It seems to me that every UserSection has a Percentage and exactly one Section.
You use Include to access the values of this Section, so I assume that Section is in a different table, with a one-to-many relation: every Section is the section of zero or more UserSections; every UserSection belongs to exaclty one Section, namely the one that the foreign key refers to.
First, try to simplify your Count, if that does not help, consider to GroupJoin.
GoodSections = assessment.UserSections
.Where(userSection => userSection.Section.SectionType != SectionTypeEnum.Reading
&& userSection.Percentage < userSection.Section.ReadinessRangeHigh
&& userSection.Percentage > userSection.Section.ReadinessRangeLow)
.Count(),
Do the GroupJoin yourself:
var test = dbContext.UserAssessments.GroupJoin(
dbContext.UserSections,
userAssessment => userAssesment.Id // from every Assessment take the primary key
userSection => userSection.AssesmentId, // from every UserSection take the foreign key
// parameter resultSelector: from every UserAssesment, with all its UserSections
// make one new
(userAssessment, userSectionsOfThisAssessment) => new
{
UserAssessmentID = userAssessment.ID,
StartedDateTime = userAssessment.StartDateTime,
CompletedDateTime = userAssessment.CompletedDateTime,
// To get the name, we need to get the Assesment that my foreign key refers to
AssessmentName = dbContext.Assessments
.Where(assessment => assessment.AssessmentId == userAssesment.Id)
.Select(assessment => assessment.Name)
.FirstOrDefault(),
GoodSections = ... // TODO
});
I'm not sure, but it seems to me that there is a oney-to-many relation between Sections and UserSections: every Section has zero or more UserSections; every UserSection belongs to exactly one Section, namely the Section that the foreign key refers to.
So for every userSectionOfThisAssessment we need to get the Section that the foreign key refers to: a standard inner join
GoodSections = userSectionsOfThisAssessment.Join(
dbContext.Sections
userSection => userSection.SectionId, // take the foreign key to the section
section => section.Id, // take the sections's primary key
(userSection, section) => new
{
SectionType = section.SectionType,
Percentage = userSection.Percentage,
MaxPercentage = section.ReadinessRangeHigh,
MinPercentage = section.ReadinessRangeLow,
})
.Where(joinResult => joinResult.SectionType != SectionTypeEnum.Reading
&& joinResult.Percentage < MaxPercentage
&& joinResult.Percentage > MinPercentage)
.Count(),
For the GoodSections, we need to GroupJoin the userSectionsOfThisAssessment with all Sections. I'm not sure if this is a one-to-many relation, or a man
MyAssessments = await test.Select(assessment => new MyAssessmentVM()
{
Assessment = assessment.Assessment.Name,
CompletedDateTime = assessment.CompletedDateTime,
StartedDateTime = assessment.StartDateTime,
UserAssessmentID = assessment.ID,
GoodSections = assessment.UserSections.Where(userSection => userSection.Section.SectionType != SectionTypeEnum.Reading)
.Count(n => n.Percentage < n.Section.ReadinessRangeHigh && n.Percentage > n.Section.ReadinessRangeLow)
}).ToListAsync();

Related

Add non existing item to list

I am trying to fill a list with objects which haven't been added yet by random to a list. So I loop rInt times through a list and want to pick randomly objects and add them to the list if they does not already exist:
collectionList = new List<CollectionSccmCM>();
Random r = new Random();
int rInt = r.Next(0, 5);
for(int i=0; i<=rInt; i++){
collectionList.Add(_context.CollectionApplications.OrderBy(x => Guid.NewGuid()).Where(x => collectionList.Any(y => y.CollectionID !=(x.collection_id.ToString()))).Select(x => new CollectionSccmCM(){CollectionID= x.collection_id.ToString(), Name=x.collection_name}).FirstOrDefault());
}
I seems that I have a mistake in the orderby and where part, but I cannot figure out the error. When I put a toList between I dont receive any syntax error anymore, but also doesn't work.
Any tip what I am doing wrong?
Thanks
Edit:
I did a mistake and had to use contains, but still not working:
collectionList.Add(_context.CollectionApplications.OrderBy(x => Guid.NewGuid()).Where(x => collectionList.Any(y => !y.CollectionID.Contains(x.collection_id.ToString()))).Select(x => new CollectionSccmCM(){CollectionID= x.collection_id.ToString(), Name=x.collection_name}).FirstOrDefault());
Edit:
Got it working with a select, but not so happy with it and dont understand why the otherone wasnt working.
collectionList.Add(_context.CollectionApplications.OrderBy(x => Guid.NewGuid()).Where(x => !collectionList.Select(y => y.CollectionID).ToList().Contains(x.collection_id.ToString())).Select(x => new CollectionSccmCM(){CollectionID= x.collection_id.ToString(), Name=x.collection_name}).FirstOrDefault());
I would strongly suggest you consider adding some whitespace to your LINQ. I would break your first example down as follows:
collectionList.Add(
_context.CollectionApplications
.OrderBy(x => Guid.NewGuid())
.Where(x => collectionList.Any(y => y.CollectionID !=(x.collection_id.ToString())))
.Select(x => new CollectionSccmCM() {
CollectionID = x.collection_id.ToString(),
Name = x.collection_name
}).FirstOrDefault()
);
Looking at your Where call, you are including in the possible elements to add, those elements where any of the collection IDs doesn't match (collectionList.Any(y => ... )). That's all of them (unless you only have one element in collectionList).
You probably want to use All instead of Any -- where all of the collection IDs don't match:
.Where(x => collectionList.All(y => y.CollectionID != x.collection_id.ToString()))

how to set value in List with out using foreach loop

I am having two lists and filter based on a group of values.
var UserIdList = response.Users.Select(p => p.Id).ToList();
var filteredRecords =
(from o in om.Organizations join u in om.Users on o.Id equals u.OrganizationId where UserIdList.Contains(u.Id)
select new { Enabled = o.Enabled, Id = u.Id }).ToList();
Now i want to set 'Exists' property in 'response.Users' to true if 'Id' exists in filteredRecords.
Please let me know how can I set value with out using foreach loop.
I have tried with
response.Users.Where(x => x.Exists = (filteredRecords .Any(z => z.Id == x.Id))).ToList();
but could not succeed as it is giving only filter results.
I want full records which are matched and which are not
Linq doesn't really support update scenarios as it's for querying data.
For lists however there's a ForEach extension method:
UserList
.ToList()
.ForEach(item=> item.Exists = filteredRecords.Any(f=> f.Id == item.Id));
Is this clearer to read and easier to understand than a foreach loop...
Edit (after question updates)
// You can't use this... it won't even compile,
// x.Exists = ... is an assignment not a condition
response.Users.Where(x => x.Exists = (filteredRecords .Any(z => z.Id == x.Id))).ToList();
What you want to do is to work with a subset of users and update the values.
response
.Users
.Where(x=> filteredRecords.Any(z => z.Id == x.Id))
.ToList() // This materialises your IEnumerable/IQueryable to allow the ForEach extension method
.ForEach(x => x.Exists = true);
you could of course use:
var usersToEnable = response.Users.Where(x=> filteredRecords.Any(z => z.Id == x.Id);
foreach(var user in usersToEnable)
user.Enabled = true;
As a direct response to:
Please let me know how can I set value with out using foreach loop. I have tried with
response.Users.Where(x => x.Exists = (filteredRecords .Any(z => z.Id == x.Id))).ToList(); but could not succeed as it is giving only
filter results. I want full records which are matched and which are
not
What you've done there is selected a collection of response user items you wish to have Exists set to true.
Now you need to set that bool in this filtered collection, and then return the full response collection afterwards, instead of returning the filtered collection.
I think this is where you're getting confused.

Linq to Entities performance problem with many columns

I am having an issue with getting linq to entities to perform well. The query I have (not mine, maintaining someone's code :-)), has several includes that I've determined are all necessary for the WPF screen that consumes the results of this query.
Now, the SQL generated executes very fast and only returns one row of data. But it is returning 570 columns, and i think the performance hit is in the overhead of creating all the objects and all of those fields.
I've tried using lazy loading, but that doesn't seem to have any effect on performance.
I've tried removing any of the "include" statements that aren't necessary, but it appears that they all are needed.
here's the linq query:
var myQuery =
from appt in ctx.Appointments
.Include("ScheduleColumnProfile")
.Include("EncounterReason")
.Include("Visit")
.Include("Visit.Patient")
.Include("Visit.Patient.PatientInsurances")
.Include("Visit.Patient.PatientInsurances.InsuranceType")
.Include("Visit.Patient.PatientInsurances.InsuranceCarrier")
.Include("MasterLookup")
.Include("User1")
.Include("User2")
.Include("Site")
.Include("Visit.Patient_CoPay")
.Include("Visit.Patient_CoPay.User")
.Include("Visit.VisitInstructions.InstructionSheet")
where appt.VisitId == visitId
&& appt.MasterLookup.LookupDescription.ToUpper() != Rescheduled
&& appt.Site.PracticeID == practiceId
&& appt.MasterLookup.LookupDescription.ToUpper() != Cancelled
orderby appt.AppointmentId descending
select appt;
The SQL generate is 4000 lines long with 570 columns in the select statment and 3 or 4 Union ALLs, so I'm not going to paste it here unless someone REALLY wants to see it. Basically, i'm looking for a way to get rid of the unions if possible, and trim down the columns to only what's needed.
Help!
:-)
if anyone is keeping track, this is the solution that ended up working for me. Thanks to everyone who commented and made suggestions... it eventually lead me to what i have below.
ctx.ContextOptions.LazyLoadingEnabled = true;
var myQuery =
from appt in ctx.Appointments
where appt.VisitId == visitId
&& appt.MasterLookup.LookupDescription.ToUpper() != Rescheduled
&& appt.Site.PracticeID == practiceId
&& appt.MasterLookup.LookupDescription.ToUpper() != Cancelled
orderby appt.AppointmentId descending
select appt;
var myAppt = myQuery.FirstOrDefault();
ctx.LoadProperty(myAppt, a => a.EncounterReason);
ctx.LoadProperty(myAppt, a => a.ScheduleColumnProfile);
ctx.LoadProperty(myAppt, a => a.Visit);
ctx.LoadProperty(myAppt, a => a.MasterLookup);
ctx.LoadProperty(myAppt, a => a.User1);
ctx.LoadProperty(myAppt, a => a.User2);
ctx.LoadProperty(myAppt, a => a.PatientReferredProvider);
var myVisit = myAppt.Visit;
ctx.LoadProperty(myVisit, v => v.Patient);
ctx.LoadProperty(myVisit, v => v.Patient_CoPay);
ctx.LoadProperty(myVisit, v => v.VisitInstructions);
ctx.LoadProperty(myVisit, v => v.EligibilityChecks);
var pat = myVisit.Patient;
ctx.LoadProperty(pat, p => p.PatientInsurances);
//load child insurances
foreach (PatientInsurance patIns in myAppt.Visit.Patient.PatientInsurances)
{
ctx.LoadProperty(patIns, p => p.InsuranceType);
ctx.LoadProperty(patIns, p => p.InsuranceCarrier);
}
//load child instruction sheets
foreach (VisitInstruction vi in myAppt.Visit.VisitInstructions)
{
ctx.LoadProperty(vi, i => i.InstructionSheet);
}
//load child copays
foreach (Patient_CoPay coPay in myAppt.Visit.Patient_CoPay)
{
ctx.LoadProperty(coPay, c => c.User);
}
//load child eligibility checks
foreach (EligibilityCheck ec in myAppt.Visit.EligibilityChecks)
{
ctx.LoadProperty(ec, e => ec.MasterLookup);
ctx.LoadProperty(ec, e => ec.EligibilityResponse);
}
I would recommend creating a new Class that contains only the properties that you need to display. When you project to a new type you don't need to have Include statements, but you can still access the navigation properties of the entity.
var myQuery = from appt in ctx.Appointments
where appt.VisitId == visitId
&& appt.MasterLookup.LookupDescription.ToUpper() != Rescheduled
&& appt.Site.PracticeID == practiceId
&& appt.MasterLookup.LookupDescription.ToUpper() != Cancelled
orderby appt.AppointmentId descending
select new DisplayClass
{
Property1 = appt.Prop1,
Proeprty2 = appt.Visit.Prop1,
.
.
.
};

Groupby and where clause in Linq

I am a newbie to Linq. I am trying to write a linq query to get a min value from a set of records. I need to use groupby, where , select and min function in the same query but i am having issues when using group by clause. here is the query I wrote
var data =newTrips.groupby (x => x.TripPath.TripPathLink.Link.Road.Name)
.Where(x => x.TripPath.PathNumber == pathnum)
.Select(x => x.TripPath.TripPathLink.Link.Speed).Min();
I am not able to use group by and where together it keeps giving error .
My query should
Select all the values.
filter it through the where clause (pathnum).
Groupby the road Name
finally get the min value.
can some one tell me what i am doing wrong and how to achieve the desired result.
Thanks,
Pawan
It's a little tricky not knowing the relationships between the data, but I think (without trying it) that this should give you want you want -- the minimum speed per road by name. Note that it will result in a collection of anonymous objects with Name and Speed properties.
var data = newTrips.Where(x => x.TripPath.PathNumber == pathnum)
.Select(x => x.TripPath.TripPathLink.Link)
.GroupBy(x => x.Road.Name)
.Select(g => new { Name = g.Key, Speed = g.Min(l => l.Speed) } );
Since I think you want the Trip which has the minimum speed, rather than the speed, and I'm assuming a different data structure, I'll add to tvanfosson's answer:
var pathnum = 1;
var trips = from trip in newTrips
where trip.TripPath.PathNumber == pathnum
group trip by trip.TripPath.TripPathLink.Link.Road.Name into g
let minSpeed = g.Min(t => t.TripPath.TripPathLink.Link.Speed)
select new {
Name = g.Key,
Trip = g.Single(t => t.TripPath.TripPathLink.Link.Speed == minSpeed) };
foreach (var t in trips)
{
Console.WriteLine("Name = {0}, TripId = {1}", t.Name, t.Trip.TripId);
}

Pivot in LINQ using lambda expression

I am writing a lambda in linq for getting the pivoted data from the resulting list.For getting the pivoting columns am setting a where condion to get the value.the problem here is am getting default value if the where condtion fails.I dont want the column if the where condition fails.Kindly help me out.
var query = dataList
.GroupBy(c => c.IpAddress)
.Select(g => new
{
IPAddress = g.Key,
AssetType = g.ElementAtOrDefault(0).AssetTypeName,
AssetName = g.ElementAtOrDefault(0).AssetName,
//if where condition fails i dont need this column.
//also am giving c.DsName == "Active Calls" ,how to achieve tis dynamically
**ActiveCalls = g.Where(c => c.DsName == "Active Calls").Sum(c => c.CurrentValue),**
**HoldCalls = g.Where(c => c.DsName == "Hold Calls").Sum(c => c.CurrentValue),**
**CPU = g.Where(c => c.DsName == "CPU").Sum(c => c.CurrentValue),**
});
Why not just create a value to hold the sum and then specify its a type in another column. That way you don't have to deal with null columns. (The assumption is that only one type is valid at a time).
Value = g.Sum(c => c.CurrentValue), // Value as specified by the the DsName property.
DsName = c.DsName

Resources