I am trying to remove an item from a list. It finds the item with the above query, but it doesn't remove it from the list. I don't know why is it so?
var qry = db.AssemblyListItems
.AsNoTracking()
.Where(x => x.ProductionPlanID == (long)_currentPlan.ProductionPlan)
.ToList();
var hasbeenAssembled = db.Assembleds
.AsNoTracking()
.Where(x => x.ProductionPlanId == (long)_currentPlan.ProductionPlan)
.ToList();
foreach (var item in hasbeenAssembled)
{
qry = qry.RemoveAll(X => X.DocumentNo == item.DocumentId &&
X.ItemCode == item.KitHeaderId &&
X.ProductionPlanID == item.ProductionPlanId );
}
olvData.SetObjects(qry);
Above is a listView where i want the items to appear. The main query "qry" is on the top.
You can handle this all in one query by excluding the assembled items from the list in a subquery:
var productionPlan = (long)_currentPlan.ProductionPlan;
var qry = db.AssemblyListItems
.AsNoTracking()
.Where(item => item.ProductionPlanID == productionPlan
&& !db.Assembleds
.Any(x => x.ProductionPlanId == item.ProductionPlanID
&& x.DocumentNo == item.DocumentId
&& x.ItemCode == item.KitHeaderId))
The advantage is (as also said by others) that you don't pull AssemblyListItems into memory that you're going to discard again later. Entity Framework will be able to translate this into one SQL statement, so everything is handled efficiently by he database.
Don't include the unwanted items in the results of the query. Don't prematurely bring over query results from the database when it might be able to process the query for you.
var hasBeenAssembled = db.Assembleds
.AsNoTracking()
.Where(x => x.ProductionPlanId == (long)_currentPlan.ProductionPlan);
var qry = db.AssemblyListItems
.AsNoTracking()
.Where(x => x.ProductionPlanID == (long)_currentPlan.ProductionPlan)
.Where(ali => !hasBeenAssembled.Any(hba => hba.DocumentId == ali.DocumentNo && hba.KitHeaderId == ali.ItemCode && hba.ProductionPlanId == ali.ProductionPlanID))
.ToList();
olvData.SetObjects(qry);
Easier way to do this. Items in the first list does not exist in the second list.
from item in hasbeenAssembled
where !(qry.Any(X => X.DocumentNo == item.DocumentId &&
X.ItemCode == item.KitHeaderId &&
X.ProductionPlanID == item.ProductionPlanId))
select item;
Related
I have to fix a query which was already written in the LINQ Lambda, I found the fix in a Simple SQL Query but now I have some trouble in converting it to LINQ Query,
Here is my SQL Query
select * from RequestItem_SubRequestItem x
where x.RequestItem_key = 1 and x.SubRequestItem_key in (
select o.SubRequestItem_key
from SubRequestItem_Entitlement o
inner join SubRequestItem sr on sr.SubRequestItem_key = o.SubRequestItem_key
where o.Entitlement_key = 2 and sr.Action = 'Add' )
And below is my LINQ C# code where I am trying to insert my fixes which include inner join.
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
.Join(ARMContext.Context.SubRequestItems, subrq => subrq.SubRequestItem_key, temp => requestItemKey, (subrq, temp) => subrq == temp)
Where as previously the C# LINQ code looked like this
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
When I try to insert the JOIN in the LINQ as per my conditions then I get to see this error.
What is my mistake? Can anybody tell me a correct way to do it?
I think this should Suffice your need, although you might have to make changes to the other code which are dependent on your SubRequestItem_Entitlement table with {user, add}
please have a look at that. As I am sure you will have to make those changes.
.Join(ARMContext.Context.SubRequestItems, user => user.SubRequestItem_key, subreqItems => subreqItems.SubRequestItem_key, (user, subreqItems) => new { user, subreqItems })
.Where(Action => Action.subreqItems.Action == z.ApprovalAction)
you can use this query. I exactly matched the SQL query
var query = ARMContext.Context.RequestItem_SubRequestItem
.Where(a => a.RequestItem_key == 1 && a.RequestItem_key == (ARMContext.Context.SubRequestItem_Entitlement
.Join(ARMContext.Context.SubRequestItems,
right => right.SubRequestItem_key,
left => left.SubRequestItem_key,
(right, left) => new
{
right = right,
left = left
})
.Where(x => x.right.Entitlement_key == 2 && x.left.Action == "Add" && x.right.SubRequestItem_key == a.RequestItem_key).Select(y => y.right.SubRequestItem_key)).FirstOrDefault());
I have the following Entity Framework 2.0 query:
var user = context.Users.AsNoTracking()
.Include(x => x.UserSkills).ThenInclude(x => x.Skill)
.Include(x => x.UserSkills).ThenInclude(x => x.SkillLevel)
.FirstOrDefault(x => x.Id == userId);
var userSkills = user.UserSkills.Select(z => new {
SkillId = z.SkillId,
SkillLevelId = z.SkillLevelId
}).ToList()
Then I tried the following query:
var lessons = _context.Lessons.AsNoTracking()
.Where(x => x.LessonSkills.All(y =>
userSkills.Any(z => y.SkillId == z.SkillId && y.SkillLevelId <= z.SkillLevelId)))
.ToList();
This query evaluates locally and I get the message:
The LINQ expression 'where (([y].SkillId == [z].SkillId) AndAlso ([y].SkillLevelId <= [z].SkillLevelId))' could not be translated and will be evaluated locally.'.
I tried to solve it using userSkills instead of user.UserSkills but no luck.
Is there a way to run this query on the server?
You should try limiting the usage of in-memory collections inside LINQ to Entities queries to basically Contains on primitive value collection, which currently is the only server translatable construct.
Since Contains is not applicable here, you should not use the memory collection, but the corresponding server side subquery:
var userSkills = context.UserSkills
.Where(x => x.UserId == userId);
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonSkills.All(y =>
userSkills.Any(z => y.SkillId == z.SkillId && y.SkillLevelId <= z.SkillLevelId)))
.ToList();
or even embed the first subquery into the main query:
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonSkills.All(y =>
context.UserSkills.Any(z => z.UserId == userId && y.SkillId == z.SkillId && y.SkillLevelId <= z.SkillLevelId)))
.ToList();
Use Contains on the server then filter further on the client:
var userSkillIds = userSkills.Select(s => s.SkillId).ToList();
var lessons = _context.Lessons.AsNoTracking()
.Where(lsn => lsn.LessonSkills.All(lsnskill => userSkillIds.Contains(lsnskill.SkillId)))
.AsEnumerable() // depending on EF Core translation, may not be needed
.Where(lsn => lsn.LessonSkills.All(lsnskill => userSkills.Any(uskill => uskill.SkillId == lsnskill.SkillId && lsnskill.SkillLevelId <= uskill.SkillLevelId)))
.ToList();
I am not an great at linq by any means but I usually have no issues with a problem of this sort. I want to convert this foreach statement to a LINQ statement:
var existingKeys = new List<int>();
foreach (var taskKey in request.Keys)
{
existingKeys.AddRange(_context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(x => x.TaskGroupNameKey));
}
I thought this would do it:
var existingKeys = request.Keys.ForEach(taskKey => _context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(x => x.TaskGroupNameKey));
That apparently returns a void not a list...
This:
var existingKeys = request.Keys.Select(taskKey =>
_context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(keys => keys.TaskGroupNameKey));
Gives me an "IEnumerable<IQueryable<int>>. So what is the secret sauce that I am missing here?
You shouldn't be performing N database queries in the first place. Using LINQ to perform those N queries instead of a foreach loop doesn't fix that core problem.
You need to re-conceptualize your query so that you have just one query that gets all of the data that you need. In this case that means getting all of the items that match your collection of keys rather than trying to match a single key and then performing N of those queries.
var requestedKeys = request.Keys;
var existingKeys = _context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key &&
requestedKeys.Contains(x.TaskKey))
.Select(x => x.TaskGroupNameKey))
.ToList();
var existingKeys = request
.SelectMany(r => r.Keys)
.SelectMany(tk =>
_context.WebTaskGroups
.Where(x.TaskGroupNameKey == key && x.TaskKey == tk)
.Select(x => x.TaskGroupNameKey))
.ToList();
var existingKeys = _context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && request.Keys.Contains(x.TaskKey))
.Select(x => x.TaskGroupNameKey)
.ToList();
ForEach return a void: http://msdn.microsoft.com/en-us/library/bwabdf9z(v=vs.110).aspx
ForEch: Performs the specified action on each element of the List.
So what to do, is for each item in the list of request.Keys to perform the action to add to the list of existingKeys.
For example:
request.Keys.ForEach(taskKey =>
existingKeys.AddRange(_context.WebTaskGroups
.Where(x => x.TaskGroupNameKey == key && x.TaskKey == taskKey)
.Select(x => x.TaskGroupNameKey));
What I'm doing now is getting my SQL database table: IsAcross varchar(45) into my if/else statement. My IsAcross table only consist of YES and NO.
So now I want to take out only select YES statement from SQL Server, so therefore I have put the thingy in the whole list first. Then I use if/else statement to extract out the YES but how am I supposed to do that?
Example: I have a total of 7 items in a list, 4 yes 3 no. I want to take out the all the 4 yes only. Something like that:
ViewModels.WordSearchVM wsvm = new ViewModels.WordSearchVM();
wsvm.ActivityID = id;
var results = db.CrossPuzzles.Where(m => m.ActivityID == id)
.Select(m => m.IsAcross)
.AsEnumerable()
.ToList();
if (results = "yes")
{
else
}
As far as I understand your problem, you can forget your if statement and simply extend the .Where part:
.Where(m => m.ActivityID == id && m.results=="yes")
var results = db.CrossPuzzles.Where(m => m.ActivityID == id)
.Where(m => m.IsAcross)
.AsEnumerable()
.ToList();
//OR
var results = db.CrossPuzzles.Where(m => m.ActivityID == id)
.Where(m => m.IsAcross == "YES")
.AsEnumerable()
.ToList();
var results = db.CrossPuzzles
.Where(m => m.ActivityID == id)
.Select(m => m.IsAcross)
.Where(x => x == "YES") // filter to "YES"
.ToList();
if (results.Count > 0)
// YES
else
// NO
This what you're looking for?
I'm currently trying doing the following.
var groups = MileId == null ? test.Groups.Where(x => x.ProjectId == ProjectId)
: test.Groups.Where(x => x.Milestone == MileId &&
x.ProjectId == ProjectId);
But I also have additional terms that I need to filter groups by:
foreach (var ChartItem in ChartItems)
{
foreach (var StatusItem in ChartItem.ChartStatusItems)
{
foreach (var PriorityItem in StatusItem.ChartPriorityItems)
{
filteredgroups.AddRange(
groups.Where(x => x.Status == StatusItem.StatusID
&& x.Priority == PriorityItem.PriorityID));
}
}
}
This is fine and it works but the nested foreach loops is pretty slow when adding the ranges. If I groups.toList() before the loop, then that statement is slow and the nested loops are fast.
My question is:
Would it be possible to filter groups from the start based on those StatusIds and PriorityIds dynamically? How?
Stackoverflow recommends some articles on Expression Tree's based on my subject line... is that what I need to look into?
Thank you
EDIT:
So I'm doing this now:
foreach (var ChartItem in ChartItems)
{
foreach (var StatusItem in ChartItem.ChartStatusItems)
{
foreach (var PriorityItem in StatusItem.ChartPriorityItems)
{
var groups = MileId == null ? test.Groups.Where(x => x.ProjectId == InspectorProjectId &&
x.Status == StatusItem.StatusID &&
x.Priority == PriorityItem.PriorityID)
: test.Groups.Where(x => x.Milestone == InspectorMileId &&
x.ProjectId == InspectorProjectId &&
x.Status == StatusItem.StatusID &&
x.Priority == PriorityItem.PriorityID);
filteredgroups.AddRange(groups);
}
}
}
It's a big improvement but it's still going to the slow 'test' server for each priority. If I could get it all filtered in 1 go, it would be ideal.
EDIT 2: Oh I don't have access to the db directly :( we access it through an API.
All this should be happening in the database. Just create a view that joins all those tables. It's hard to be faster than a database when intersecting and joining sets of data.
Can you do it with Contains?
var filteredgroups =
test.Groups.Where(x =>
(MileId == null || x.Milestone == MileId) // (replaces ?: in original)
&& x.ProjectId == ProjectId
&& ChartItem.ChartStatusItems.Contains(x.Status)
&& StatusItem.ChartPriorityItems.Contains(x.Priority));
(I'm not sure how Linq-to-Sql and Linq-to-Objects are going to interact wrt performance, but at least it's concise...)
Maybe you can call .Any() within your .Where() and skip the loops entirely.
test.Groups.Where(x => (MileId == null ||
x.Milestone == MileId) &&
x.ProjectId == ProjectId &&
ChartItems.Any(c => c.ChartStatusItems.Any(s => s.StatusId == x.StatusId &&
s.ChartPriorityItems.Any(p => p.PriorityId == x.PriorityId))));
The foreach loops are most likely executing a deferred call, which is most likely hitting your database on each foreach loop. But you don't have to, using SelectMany you can simply build up your query:
var statuses = ChartItems
.SelectMany(x => x.ChartStatusItems)
.Select(i => i.StatusId);
var priorities = ChartItems
.SelectMany(x => x.ChartPriorityItems)
.Select(i => i.PriorityId);
var filtered = groups.Where(x => statuses.Contains(x.Status) &&
priorities.Contains(x.Priority))