I have a list containing some objects and want to use LINQ to remove a specific item but am not sure how to do that.
foreach (var someobject in objectList)
{
if (someobject.Number == 1) // There will only one item where Number == 1.
{
list.remove(someobject)
}
}
You cannot use a foreach to remove items during enumeration, you're getting an exception at runtime.
You could use List.RemoveAll:
list.RemoveAll(x => x.Number == 1);
or, if it's actually not a List<T> but any sequence, LINQ:
list = list.Where(x => x.Number != 1).ToList();
If you are sure that there is only one item with that number or you want to remove one item at the maximum you can either use the for loop approach suggested in the other answer or this:
var item = list.FirstOrDefault(x => x.Number == 1);
if(item ! = null) list.Remove(item);
The link to the other question i have posted suggests that you can modify a collection during enumeration in C# 4 and later. No, you can't. That applies only to the new concurrent collections.
You can't use foreach to remove item from collection. This will throw an exception that the collection is modified.
You can perform it with for
for (int i=objectList.Count-1; i>=0 ; i--)
{
if (objectList[i].Number == 1) // there will only one item with Number = 1
{
objectList.Remove(objectList[i]);
}
}
Other case is to use Remove/RemoveAll like Tim Schmelter show.
You CAN use a foreach loop to remove an item from a list. The important thing is to use break to stop the loop once you have removed the item. If the loop continues after an item is removed from the list, an exception will be thrown.
foreach (var someobject in objectList)
{
if (someobject.Number == 1) // There will only one item where Number == 1
{
objectList.remove(someobject);
break;
}
}
However, this will ONLY work if there's only one object you want to remove, as in your case.
you could remove at the index if the value meets the criteria
for (int i=0; i < objectList.Count; i ++)
{
if (objectList[i].Number == 1) // there will only one item with Number = 1
{
objectList.RemoveAt[i];
}
}
Related
I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
I have a datatable with the following information:
365.00
370.00
369.59
365.00
365.00 -> match with previous item
365.00 -> match with previous item
I only need to remove the next matched items, like this:
365.00
370.00
369.59
365.00
I tried:
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.DistinctBy(i => i.articlepricehistory_cost)
.ToList();
Result:
365.00
370.00
369.59
Any ideas?
Another approach:
public static IEnumerable<T> MyDistinct<T>(this IEnumerable<T> items)
{
T previous = default(T);
bool first = true;
foreach(T item in items)
{
if (first || !Equals(previous, item))
{
first = false;
previous = item;
yield return item;
}
}
}
Or, as requested, with a selector
public static IEnumerable<T> MyDistinct<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
U previous = default(U);
bool first = true;
foreach(T item in items)
{
U current = selector(item);
if (first || !Equals(previous, current))
{
first = false;
previous = current;
yield return item;
}
}
}
Here's a neat LINQ solution for u
var list = (dt as Enumerable);
var numbers = list.TakeWhile((currentItem, index) => currentItem != list.ElementAtOrDefault(index - 1));
Keep in mind if u have 0 as the first element it will be ommitted from the new list since ElementAtOrDefault will return 0 in the first iteration of the while loop (index of -1), thus evaluating the expression to false. A simple if statement can help you avoid this.
Here's an idea I have not actually tried
Do a Skip(1) on the query to produce a second query.
Now append to the second query any element not equal to the last element in the first query, to produce a third query.
Now zip join the first and third queries together to form a set of pairs; this is the fourth query.
Now construct a fifth query that filters out pairs that have identical elements from the fourth query.
Finally, construct a sixth query that selects the first element of each pair from the fifth query.
The sixth query is the data set you want.
The problem in your query is that you are using .DistinctBy() which will return distinct results only. So if 365.00 appeared anywhere, it won't show up in the returned list again.
var differentPreviousList = new List<double>();
var itemPriceList = dt.ToList();
differentPreviousList.Add(itemPriceList[0]);
for (var index = 1; index < itemPriceList.Count; index++)
{
if (itemPriceList[index - 1] == itemPriceList[index]) continue;
differentPriceList.Add(itemPriceList[index]);
}
It may not be an elegant solution but I would just parse a bare query..here is how.
Run a lambda query to get all the original results without trying to filter out DistinctBy.
Create an object of a single query result of the type you initially queried for.
Initialize a new list for foreach parse results.
Do a for each loop for each result.
The first if section should be if(object above loop is null).
IF is true add item to list
ELSE if check to see if value of item is same as the last iteration.
Store foreach iteration object to the object declared before the loop.
Rinse and repeat, and the result is no duplicate objects found in a row in the loop will be stored in the list resulting in what you wanted.
I think you have to use a temporary value to check if the next value matches the current value or not.
double temporary = -1; // temp value for checking
List<double> results = new List<double>(); // list to store results
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.Select(a => a.articlepricehistory_cost)
.ToList()
.ForEach(cost =>
{
if (temporary != cost) { results.Add(cost); }
temporary = cost;
});
foreach (var result in results)
{
Console.WriteLine(result);
}
After ForEach method is equivalent to the following.
foreach (var cost in costs)
{
if (temporary != cost)
{
results.Add(cost);
Console.WriteLine(cost);
}
temporary = cost;
}
With the following structure
[[1,10],[2,20],[5,45],[10,34]]
this foreach loop finds the first element that matches "planYear". If planYear=5 then the third element value of "45" would be selected.
List<object> gifts = gifts;
foreach (List<object> item in gifts)
{
if (item[0] == planYear)
{
gift = Convert.ToDouble(item[1]);
break;
}
}
What would be an analogous Linq statement to achieve this same result?
var gift = gifts.Cast<List<object>>()
.Where(x => x[0] == planYear)
.Select(x => Convert.ToDouble(x[1]))
.FirstOrDefault();
If no matching entry has been found gift will be 0. If that's not what you want, use First() instead. This will throw an exception if no matching item exists.
This answer assumes - just like your foreach loop - that every item inside gifts is actually a List<object>. If even one item is of a different type, this code will throw an InvalidCastException. If this is a problem, use OfType instead of Cast.
var gift = Convert.ToDouble(
gifts.Cast<List<object>>().First(x => x[0] == planYear)[1]);
Is there a way to do the following using Linq:
foreach (var c in collection)
{
if (c.Condition == condition)
{
c.PropertyToSet = value;
// I must also check I only set this value to one minimum and only one element.
}
else
{
c.PropertyToSet = otherValue;
}
}
To clarify, I want to iterate through each object in a collection and then update a property on each object except for one element of my collection that should updated to another value.
At this moment I use a counter to check I set my value to one and only one element of my collection. I removed it from this example to let people suggest other solutions.
The original question without exception in collection is here
EDIT
I ask this question because I'm not sure it's possible to do it with LinQ. so your answers comfort my opinion about LinQ. Thank you.
You can use .ForEach to make the change, and .Single to verify only one element matches the condition:
// make sure only one item matches the condition
var singleOne = collection.Single(c => c.Condition == condition);
singleOne.PropertyToSet = value;
// update the rest of the items
var theRest = collection.Where(c => c.Condition != condition);
theRest.ToList().ForEach(c => c.PropertyToSet = otherValue);
I don't suggest you to implement this with Linq. Why? Because Linq is for querying, not for modification. It can return you objects which match some condition, or objects which don't match. But for updating those objects you still need to use foreach or convert query results to list and use ForEach extension. Both will require enumerating sequence twice.
So, simple loop will do the job:
foreach (var c in collection)
{
c.PropertyToSet = (c.Condition == condition) ? value : otherValue;
}
collection.Where(x => <condition>).ToList().ForEach(x => <action>);
Hacky way to use LINQ if you persist to use:
var result = collection.Select(c =>
{
c.PropertyToSet = c.Condition == condition ? value : otherValue;
return c;
});
But my recommendation, don't do this, you code actually get the best approach, for more readability, you can change:
foreach (var c in collection)
c.PropertyToSet = c.Condition == condition ? value : otherValue;
You can use a ternary operator in conjunction with a linq statement:
collection.ToList().ForEach(c => c.PropertyToSet = c.Condition == condition ? value : otherValue);
However I woud just use a regular foreach here to avoid converting the collection to a list.
Well, you could do:
var itemToSetValue = collection.FirstOrDefault(c => c.Condition == condition);
if(itemToSetValue != null)
itemToSetValue.PropertyToSet = value;
// Depending on what you mean, this predicate
// might be c => c != itemToSetValue instead.
foreach (var otherItem in collection.Where(c => c.Condition != condition))
{
otherItem.PropertyToSet = otherValue;
}
Now of course that's not a pure LINQ solution, but pure LINQ solutions are not appropriate for modifying existing collections.
I'm having trouble here. I have an observable collection, and I want to delete some of the stuff in it if it meets a certain criteria.
Here's the code:
foreach (Record record in SwearJarController.Records)
{
if (record.Word == "Hi")
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
}
Records is the name of the collections, and record is the class. Apparently, it gets confused at the 'foreach' part.
What should I do?
Thanks!
You cannot modify a collection that you are enumerating. You must do a ToArray() or ToList() on your foreach:
foreach (Record record in SwearJarController.Records.ToArray())
{
if (record.Word == "Hi")
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
}
Cleaned up version:
foreach (Record record in SwearJarController.Records
.Where(x => String.Equals(x.Word, "Hi")
.ToArray())
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
Note: You should really use String.Equals(...) and use a StringComparison enumeration as well. I'm not sure if your comparison was meant to be case sensitive and/or ordinal/currentculture/invariantculture.
You can't modify the collection while you are working on it. You can run a for loop with the count going from Records.Count down to 0 and remove that way. That should work
Example:
for(int x = SwearJarController.Records.Count - 1 ; x >= 0; x--)
{
Record record = SwearJarController.Records[x];
if(record.Word == "Hi")
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
}
As others have already said, you can't modify a collection while you are iterating thru it.
Your best bet for deleting your records cleanly is to do the following:
foreach (var record in SwearJarController.Records
.Where(r => r.Word == "Hi")
.ToArray())
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
This filters and snapshots your SwearJarController.Records collection before deleting.
Doing the filter first limits the possibility of a large array being created.