I am trying to find the indexes as follows.
But the following LINQ says there is no extension of FindIndex.
foreach (var distinctClassId in distinctClassIds)
{
var indexes = classComponents.Where(x=> x.ClassId.Equals(distinctClassId)).
FindIndex(x => x.Id == Constants.Classes.ClassID.ToString() ||
x.Id == Constants.Courses.CoursesID.ToString());
}
FindIndex() is an instance method of List<T>. But Where() returns an IEnumerable<T> (or maybe an IQueryable<T> depending on what classComponents is in your code).
So you need to convert that Where result to list before you can call FindIndex():
foreach (var distinctClassId in distinctClassIds)
{
var indexes = classComponents.Where(x=> x.ClassId.Equals(distinctClassId)).ToList(). // here -> ToList()
FindIndex(x => x.Id == Constants.Classes.ClassID.ToString() ||
x.Id == Constants.Courses.CoursesID.ToString());
}
But FindIndex() only gives you the index of the first matching item. Your variable is called indexes, so you might want to find the indices of multiple matching items. This can be done by using the linq overloads that give you the index, too:
foreach (var distinctClassId in distinctClassIds)
{
var filtered = classComponents.Where(x=> x.ClassId.Equals(distinctClassId)).ToList();
var indexes = filtered.Select((item, index) => new {item, index})
.Where(x => x.item.Id == Constants.Classes.ClassID.ToString() ||
x.item.Id == Constants.Courses.CoursesID.ToString())
.Select(x => x.index);
}
This creates objects of anonymous type containing the item and its index, then checking which items match and finally returning their indices.
Note: if you actually wanted the indices in the source list (classComponents), you should combine the Where statements like that:
foreach (var distinctClassId in distinctClassIds)
{
var indexes = classComponents.Select((item, index) => new {item, index})
.Where(x => x.item.ClassId.Equals(distinctClassId) &&
(x.item.Id == Constants.Classes.ClassID.ToString() ||
x.item.Id == Constants.Courses.CoursesID.ToString()))
.Select(x => x.index);
}
Related
var listOfIds = new List<string>();
var allItems = IEnumerable<Info>();
foreach (var id in collectionIds)
{
listOfIds.AddRange(allItems
.Where(p => p.Data.FirstOrDefault(m => m.Key == "myId").Value == id)
.Select(x => x.Id));
}
I would like to avoid using AddRange but use only Add in this case and maybe use only FirstOrDefault in the place of where to avoid the last Select case.
Is this possible and if yes how?
Assuming your original code is giving you the correct data, specifically you are OK with:
Only concerned that the first item in p.Data contains a matching value and;
p.Data will always contains at least a single element.
Then this code will give you the same output:
var listOfIds = allItems
.Where(p => collectionIds.Contains(p.Data.First(m => m.Key == "myId").Value))
.ToList();
However, if you really do care that any value in p.Data matches, then this would be more appropriate:
var listOfIds = allItems
.Where(p => p.Data.Any(m => m.Key == "myId" &&
collectionIds.Contains(m.Value)))
.ToList();
How about this approach:
var listOfIds = new List<string>();
var allItems = IEnumerable<Info>();
var groupedAllItems = allItems.GroupBy(x => x.Data.FirstOrDefault(m => m.Key == "myId")?.Value ?? "MyIdNotFound");
//collectionIds should be of type HashSet<string> for the contains to be fast
listOfIds.AddRange(groupedAllItems.Where(x => collectionIds.Contains(x.Key)).SelectMany(x => x));
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;
So, I have a list of objects (let's say there are 20) and they have an id. Then I have another list (which is ordered correctly).
I had this linq to sort the object list by the id list:
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.OrderBy(m => outcomeIds.IndexOf(m.Id))
.ToList();
Now, this "would" work, but the problem is the outcomeIds only has a selection of ids in it. I would have thought that indexOf would return -1 for any id that was not found and it would be put under the matched ids. Instead they appear first in the list.
How can I modify my code to get the matching ids at the top and the rest at the bottom. I can't do a reverse, because it would mean that the order of the matching ids would be in reverse too.
Sounds like you want to order by the result of IndexOf, but to have the -1 values go to the end instead of the start. In that case, you could just process the value of the IndexOf to, say, int.MaxValue so it'll go at the end.
I've tidied up your code a bit to make it more readable - only the OrderBy is different to your original code.
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.Select(m => Tuple.Create(m, outcomeIds.IndexOf(m.Id))
.OrderBy(m => outcomeIds.IndexOf(m.Id) == -1 ? int.MaxValue : outcomeIds.IndexOf(m.Id))
.ToList();
Or, if you don't want to call IndexOf multiple times, you could extract the conditional statement into a method:
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.Select(m => Tuple.Create(m, outcomeIds.IndexOf(m.Id))
.OrderBy(m => orderByKeySelector(outcomeIds(m.Id)))
.ToList();
where orderByKeySelector is
private static int orderByKeySelector<T>(List<T> source, T value)
{
var indexOfValue = source.IndexOf(value);
return indexOfValue == -1 ? int.MaxValue : indexOfValue;
}
var outcomeIds = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select(m => m.Id)
.ToList();
groupResponseModel.Outcomes = groupOutcomes
.OrderBy(m => outcomeIds.IndexOf(m.Id) != -1
? outcomeIds.IndexOf(m.Id)
: outcomeIds.Max())
.ToList();
I prefer keeping it simple:
var outcomeList;
var unorderedList;
//check all elements of the ordered list in order
foreach(var item in orderedList)
{
//if your unordered list has this item
if(unorderedList.Any(item))
{
//add this item to the final list
outcomeList.Add(item);
//and remove it from unordered
unorderedList.Remove(item);
}
}
//at this point, you added all your matching entities in order, the rest is the remainder:
outcomeList.AddRange(unorderedList);
You can even turn this into an extension method for reusability.
Why not using mapping (say, id == 5 corresponds to 0, id = 123 to 1 etc.) with a help of dictionary? It will be efficient in case of long lists:
var order = outcomeRequestModels
.OrderByDescending(m => m.Score)
.Select((m, index) => new {
id = m.id,
index = index })
.ToDictionary(item => item.id, // id
item => item.index); // corresponding index
Now let's sort the 2nd list:
groupResponseModel.Outcomes = groupOutcomes
.OrderBy(m => order.TryGetValue(m.Id, out var order)
? order // if we have corresponding index, use it
: int.MaxValue) // otherwise, put the item at the bottom
.ToList();
I wanna convert query syntax to fluent syntax but I can't do it. I don't know can I use two clauses "where" in fluent syntax in this code?
public static void Ex6()
{
string[] csvRecipes = { "milk,sugar,eggs", "flour,BUTTER,eggs", "vanilla,ChEEsE,oats" };
var dairQuery=
from csvRecipe in csvRecipes
let ingredients = csvRecipe.Split(',')
from ingredient in ingredients
let uppercaseIngredient=ingredient.ToUpper()
where uppercaseIngredient =="MILK" ||
uppercaseIngredient == "BUTTER" ||
uppercaseIngredient == "CHEESE"
select uppercaseIngredient;
foreach (var item in dairQuery)
{
Console.WriteLine("{0} is dairy", item);
}
}
I think this does pretty much, what you want.
var query = csvRecipes.Select(x => x.Split(','))
//flatten the list
.SelectMany(y => y)
//ToUppercase Again
.Select(x => x.ToUpper())
.Where(i => i == "MILK" ||
i == "BUTTER" ||
i == "CHEESE");
If you want to compare strings ignoring the ordinal case, you can use i => i.Equals("MILK", StringComparison.OrdinalIgnoreCase)
can I use two clauses "where" in fluent syntax in this code?
You can chain Where clauses, but then they are interpreted as AND clauses instead of OR clauses (since the data must pass through all Where filters).
The appropriate way to convert that where clause to fluent would be:
.Where(item => item.uppercaseIngredient == "MILK" ||
item.uppercaseIngredient == "BUTTER" ||
item.uppercaseIngredient == "CHEESE")
Alternatively you could use a collection of items and Contains:
var validTypes = new string[] { "MILK", "BUTTER", "CHEESE"};
...
.Where(item => validTypes.Contains(item.uppercaseIngredient))
I'd use a collection where you store the ingredients you want to select. Then you can use SelectMany to find all matching tokens with this concise query:
string[] ingredients = new[] { "MILK", "BUTTER", "CHEESE" };
var dairQuery = csvRecipes
.SelectMany(r => r.Split(',').Select(i => i.ToUpper()).Where(ingredients.Contains));
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));