NEWBIE : Selecting 2 or more information from IEnumerable Select - select

Newbie here, I have a problem with my code. Before this code returns an array of string (student name) but I need to add the student id without requiring me to recode the entire method. Can someone help me on how to do it? Basically I need to include the Student ID based on the Student Name.
TIA.
public Student[] GetAllStudents(string subject)
{
Student[] students = cache.GetAllStudents(subject);
if (students == null)
{
Subjects group = RetrieveSubjects(subject);
if (group != null)
{
students = group.Students.Select(r => r.StudentName).ToArray();
// I need to include also the Student ID based on the Student Name queried above.
}
else
{
students = new string[0];
}
cache.AddAllStudents(subject, students);
}
return students;
}

You can use anonymous class in your select clause, ie
students = group.Students
.Select(r => new { name= r.StudentName, id= r.StudentID } )
.ToArray();
Or just select Student object as it should has all you need
students = group.Students.ToArray();
// doing .Select(r=>r) is redundant and can be omited
Seem that you need to create Student class from your studentinfo class, you would want to change prop's in initializer.
students = group.Students
.Select(r => new Student {
StudentName= r.StudentName,
StudentID= r.StudentID
})
.ToArray();

Related

How to optimize the query - Ef core

I am working a asp .net core web API with EF core.
I wrote this query. But this take 20-30seconds to execute.
Anyone have idea to improve this query.
var hotels = await _context.Hotels
.Where(i => (i.DestinationCode == request.Destination))
.Select(i => new HotelListHotelVm
{
Item1 = i.Item1,
Item2 = i.Item2,
Item3 = i.Item3,
Item4Code = i.Item4Code,
Item4Description = i.Item4.TypeDescription,
Item5 = i.Item5.Select(x => new HotelListHotelVm.HotelListItem5Vm
{
Code = x.Item5Code,
Description = x.Item5.Description,
}).Where(x =>(incomingItem5s.Length > 0 ) ? (incomingItem5s.Contains(x.Code)) : (x.Code != "")),
Item6 = i.Item6.Select(x => new HotelListHotelVm.HotelListHotelItem6Vm
{
Id = x.Id,
Item6TypeCode = x.Item6TypeCode,
Order = x.Order,
Path = x.Path,
VisualOrder = x.VisualOrder,
}).Take(3),
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode
}),
})
.Where( i => ((incomingItem4.Length > 0 ) ? (incomingItem4.Contains(i.Item4Code)) : (i.Item4Code != "")) )
.OrderByDescending(i => i.Code)
.PaginatedListAsync(request.PageNumber, request.PageSize);
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach( var fac in _context.Facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
I f I remove those foreach code, The query takes 8-10s to execute.
But I need those foreach codes. Because I need the HotelFacilityDescription
Any suggestion for optimize the query ?
Edit The i.Facilities - model
public class HotelFacility
{
// removed some
public int FacilityCode { get; set; }
public int FacilityGroupCode { get; set; }
public FacilityGroup FacilityGroup { get; set; }
public int HotelCode { get; set; }
public Hotel Hotel { get; set; }
}
}
_context.Facilities will be enumerated (i.e. database will be called) for every iteration of previous loops. The quick fix is to call it ones and store results in variable:
var facilities = _context.Facilities.ToList();
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach(var fac in facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
Next improvement can be converting facilities into Dictionary for searching purposes.
Even better approach can be writing query joining with _context.Facilities on database side (but here more info needed).
I've read this a couple times, but it looks like the relationship for Hotel.Facilities is a Facility, so could you not just do:
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.Description
}),
If for some reason Hotel.Facilities is not pointing at a Facility, but is a Many-to-Many HotelFacilityGroup entity to a FacilityGroup, that also contains a FacilityCode, if the associated FacilityGroup has access to a set of Facilities beneath it you could leverage that:
Edit: It sounds like multiple Facilities share the same Code where some may have a null description. Provided that the facilities matching the code would be within the same facility group and not consider the same Code within different facility groups. If you need to match the code across all facilities then there probably isn't much of an alternative to loading the entire set of facility codes & descriptions.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.FacilityGroup.Facilities.Where(f => f.Code == x.FacilityCode && f.Description != null).Select(f => f.Description).FirstOrDefault()
}),
That would avoid the need to go load all of the facilities to resolve that code. Otherwise, if you do need to fetch across all facilities, pre-loading them would be the way to go, but rather than fetching the entire Facility entity I would recommend just the values you need, the Code and the Description. This cuts down on the amount of memory needed and potentially be a faster query:
var facilities = _context.Facilities
.Select(f => new
{
f.Code,
f.Description
}).ToList();
Edit:
From there, finding a match using:
foreach( var facility in hotels.Items.SelectMany(x => x.HotelFacilities)
{
facility.HotelFaciltyDescription = facilities
.Where(x => x.Code == facility.FacilityCode
&& !string.IsNullOrEmpty(x.Description)
.Select(x => x.Description)
.FirstOrDefault();
}
I would recommend an OrderBy clause to ensure the selection of the facility is predictable as it sounds like there could be multiple matches on a code with a non-null description.
The loop can be eliminated by projecting the value in the LINQ to Entities query.
It would have been quite easy if you had relationship and navigation property like other *Code fields. But as clarified in the comments, there is no such relationship, so you have to resort to old good manual left other join to emulate what navigation property provide automatically, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
// left outer join with Facilities
.SelectMany(x => _context.Facilities
.Where(f => x.FacilityCode == f.Code).DefaultIfEmpty(),
(x, x_Facility) => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x_Facility.Description // <--
}),
Here x_Facility emulates optional reference navigation property x.Facility if existed.
In case you need just single property from the related table, instead of a left join you could also use the original query with single value returning correlated subquery inside the projection, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
.Select(x => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = _context.Facilities
.Where(f => x.FacilityCode == f.Code)
.Select(f => f.Description)
.FirstOrDefault() // <--
}),
or even
HotelFacilityDescription = _context.Facilities
.FirstOrDefault(f => x.FacilityCode == f.Code).Description
All these will eliminate the need of the post loop executiong additional database queries. You can test them and take the one with best performance (#2 and #3 produce one and the same SQL, so it's a matter of taste - the choice is between #1 and #2/3).

EF Core querying many-to-many relationship tables

I have a question related to querying many-to-many relationship tables.
For example Students and Courses are the main tables whereas Enrollment (stores StudentID, CourseID) is the join table.
Given a list of 5 courses on how to find the students who have enrolled exactly to those 5 courses in EF Core?
Firstly, You need to get list of enrollments like this way
var enrollments = from s in dc.Students
from c in s.Courses
select new { StudentID = s.StudentID, CourseID = c.CourseID };
Secondly, you might group by StudentId
var groupedEnrollment = enrollments.GroupBy(p => p.StudentId)
.Select(g => new
{
StudentId = g.Key,
Courses = g.Select(p => p.CourseId).ToArray()
});
Finally, Select result by condition accordingly.
var courses = new[] { 1, 2, 3, 4, 5 };
var result = groupedEnrollment.Where(g =>
g.Courses.Length == courses.Length &&
g.Courses.Intersect(courses).Count() == courses.Length);

how to get list of one entity set from another entity set in a many to many relationship

1I am working with controller code in MVC.
I have the follow code working where the are related entities priority list based on teams:
public JsonResult GetPrioritiesByTeam(int id)
{
List<Priority> priorities = new List<Priority>();
if (id > 0)
{
priorities = db.Priorities.Where(p => p.Team == id ).ToList();
}
else
{
priorities.Insert(0, new Priority { Id = 0, PriorityDescription = "--Select a Team first--" });
}
var result = (from r in priorities
select new
{
id = r.Id,
name = r.PriorityDescription
}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}
The statement
priorities = db.Priorities.Where(p => p.Team == id
does get me the list of priorities based on the team passed in by the parameter id.
I am now try to achieve the same successful result with another entity that is related to team- an entity called 'members'. However the relationship is not a one to many. Its a many to many- so I have an intermediate entity call 'teammembers'.
The Team is still the entity driving the choice. When the team is chosen, I need it to give me all the team member entries in teammembers, and then I need similar to the priorities code above, give me a members list which is based on the teammembers identified based on the team selected.
I think based on the code provided the else clause would be similar coding along with the var result coding.
I just cant seem to get the coding right for the clause in the condition for id >0.
How do I write that clause for the many to many so as to get the list of members for the team through teammembers?
You can assume entity team is Id and TeamDescription, entity Member is Id and MemberName, and entity teammember is an ID,and teamId and MemberId -just a straightforward many to many.
Been trying with LINQ method syntax and LINQ query syntax but cant get it working.
Thanks for any help you can provide
New code would look like the following except i need working id>0 clause:
public JsonResult GetMembersByTeam(int id)
{
List<Member> members = new List<Member>();
if (id > 0)
{
**members = Members.Where(needed clause).ToList();**
}
else
{
members.Insert(0, new Member { Id = 0, MemberFullName = "--Select a Team first--" });
}
var result = (from m in members
select new
{
id = m.Id,
name = m.MemberFullName
}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}
Entity Diagram
Current code with suggested changes
public JsonResult GetMembersByTeam(int id)
{
List<Member> members = new List<Member>();
if (id > 0)
{
//members = db.Teams.Where(p => p.Id == id)
// .SelectMany(e => e.TeamMembers)
// .Select(e => e.Member)
// .ToList();
members = db.TeamMembers.Where(p => p.Team == id)
.Select(e => e.Member)
.ToList();
}
else
{
members.Insert(0, new Member { Id = 0, MemberFullName = "--Select a Team first--" });
}
var result = (from m in members
select new
{
id = m.Id,
name = m.MemberFullName
}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}
results in error :
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.List,ManageHR5.model.Member>'
try this:
members = db.TeamMembers.Where(p => p.Team == id )
.Select(e => e.Member1)
.ToList();

System.LimitException: Too many SOQL queries: 101 Data Loader Issue

I have an issue with dataloader while trying to load 8,000 records that fires the following trigger that gives me an error System.LimitException: Too many SOQL queries: 101 because of this issue ..
trigger BeforeTaskTrigger on Task (after insert, after update) {
for(Task s : Trigger.new)
{
if ((s.Type == 'QRC')&&(s.Status=='Completed')) {
BusinessLogic.processUpdateInsertTask(s);
}
}
}
public static void processUpdateInsertTask (Task s){
List<Task> itemList = [select Id, Type, Status, ActivityDate, OwnerId from Task where accountId = :s.AccountId and status = 'Completed' and Type ='QRC' Order By ActivityDate Desc ];
List<Event> eventList = [select Id, Type, Status__c, ActivityDate, OwnerId, endDateTime from Event where accountId = :s.AccountId and Status__c = 'Completed' and Type ='QRC' Order By endDateTime Desc ];
List<Account> accountData = [Select Id, Last_QRC_Date__c, Last_QRC_FA__c from Account where Id = :s.AccountId];
if ((accountData!=null)&&(accountData.size()>0)){
Date eventDate;
if (eventList != null && eventList.size()>0){
eventDate = date.newinstance(eventList.get(0).endDateTime.year(), eventList.get(0).endDateTime.month(), eventList.get(0).endDateTime.day());
}
if ((itemList != null)&&(itemlist.size()>0)&&(eventList!=null)&&(eventList.size()>0)){
if (itemList.get(0).ActivityDate >= eventDate){
accountData.get(0).Last_QRC_Date__c = itemList.get(0).ActivityDate;
accountData.get(0).Last_QRC_FA__c = itemList.get(0).OwnerId;
update accountData;
}
else {
accountData.get(0).Last_QRC_Date__c = eventDate;
accountData.get(0).Last_QRC_FA__c = eventList.get(0).OwnerId;
update accountData;
}
}
else if ((itemList != null)&&(itemlist.size()>0)){
processTaskSpecialCases(accountData, itemList);
}
else if ((eventList!=null)&&(eventList.size()>0)){
processEventSpecialCases(accountData, eventDate, eventList);
}
else {
processDeletionCases (accountData);
}
}
}
I'll be glad if you can help me with rephrasing the SOQL query to be more efficient.
You need to move the queries populating itemList and eventList outside of your for loop. Traditionally, when you need information like that, you query everything you need once and then put it into a map for looking up later.
For instance:
// Get all the Account Ids
List<String> accountIds = new List<String>();
for (Task t : Trigger.new)
{
accountIds.add(t.AccountId);
}
Map<String, List<Task>> taskMap = new Map<String, List<Task>>(); // keyed by AccountId
for (Task t : [select Id, AccountId, Type, Status, ActivityDate, OwnerId from Task where accountId = :accountIds and status = 'Completed' and Type ='QRC' Order By ActivityDate Desc ])
{
List<Task> tasks = new List<Task>();
if (taskMap.containsKey(t.AccountId))
{
tasks = taskMap.get(t.AccountId);
}
tasks.add(t);
taskMap.put(t.AccountId, tasks);
}
This example based on the itemList above gets you a Map keyed by the Account ID of all the Tasks belonging to that Account. When you need to reference that list, you just key into the map and get the value (and best of all, it only counts as a single SOQL query that you can use across your entire trigger).
Take a look at the APEX Best Practices, bulkifying your code is a huge part of developing scalable Salesforce applications.

How to access Related Data built By the EF

Similar to the simple Membership UserProfiles to Roles in the Table UserInRoles
I have created a Relationship between UserProfiles to Clients in the table UserInClients with this code
modelBuilder.Entity<UserProfiles>()
.HasMany<dbClient>(r => r.Clients)
.WithMany(u => u.UserProfiles)
.Map(m =>
{
m.ToTable("webpages_UsersInClients");
m.MapLeftKey("ClientId");
m.MapRightKey("UserId");
});
My UserProfiles has an public virtual ICollection<dbClient> Clients { get; set; } and my Clients has an public virtual ICollection<UserProfiles> UserProfiles { get; set; }
If you need to see the Models let me know i can post them
In the model and view i would like to
Display all Clients ( Distinct ) and show how many users have access to that client
Create a View that only shows clients a Currently logged in user is allowed to view.
I thought it was as easy as accessing the Properties of my models Clients.ClientID, i have been trying things like Clients.ClientID.select(u=>u.UserId == clientid) but i knew better and know it does not and will not work.
My other thoughts were creating a model with CliendID and UserID in it ( like the table it created ) so i can use a join in my controller to find the right values??
In the end what i'm trying to accomlish is to populate a KendoUI CascadingDropDownList with this line in my GetCascadeClients JsonResult
return Json(db.Clients.Select(c => new { ClientID = c.ClientID, ClientName = c.Client }), JsonRequestBehavior.AllowGet);
My question is, when I'm in my Controller, how do I access this table built by Entity Framework?
EDIT:
SOLUTION QUERY Pieced together by both answers
return Json(db.Clients
.Include(c => c.UserProfiles)
.Where(c => c.UserProfiles.`Any(up => up.UserName == User.Identity.Name))`
.Select(c => new
{
ClientID = c.ClientID,
ClientName = c.Client,
UserCount = c.UserProfiles.Count()
}),
JsonRequestBehavior.AllowGet);
try something like:
return JSON (db.Clients
.Include(c => c.UserProfiles)
.Where(c => c.UserProfiles.UserId == loggedInUserId)
.Select( c => new {
ClientId = c.ClientID,
ClientName = c.Client,
UserCount = c.UserProfiles.Count()}),
JsonRequestBehavior.AllowGet);
the .Include() extension will make sure that you pull all the UserProfiles along with the Clients, allowing you to use that table for filtering, record counts, etc... that .Where clause might need some work actually, but this should be a solid start.
This is more of a LINQ question really:
db.Clients
.Where(c => c.UserProfiles.Any(up => up.UserId == loggedInUserId))
.Select(c => new {
ClientId = c.ClientID,
ClientName = c.Client + " (" + c.UserProfiles.Count() + ")"
})
Due to the fact that it will convert the above into SQL calls, I had to use string concatenation, as if you try to use a nice String.Format("{0} ({1})", c.Client, c.UserProfiles.Count()) it will complain about being unable to translate that to SQL.
The other options is to do a 2-pass query, materializing the data before doing extra formatting:
db.Clients
.Where(c => c.UserProfiles.Any(up => up.UserId == loggedInUserId))
.Select(c => new {
ClientId = c.ClientID,
ClientName = c.Client,
ProfileCount = c.UserProfiles.Count()
})
// this forces SQL to execute
.ToList()
// now we're working on an in-memory list
.Select(anon => new {
anon.ClientId,
ClientName = String.Format("{0} ({1})", anon.ClientName, anon.ProfileCount)
})