Searching and Deleting stuff from Observable Collections in C#? - c#

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.

Related

Iterate over an IQueryable without calling ToList()

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();
}

Remove a specific item from a list using LINQ

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];
}
}

Checking a list with null values for duplicates in C#

In C#, I can use something like:
List<string> myList = new List<string>();
if (myList.Count != myList.Distinct().Count())
{
// there are duplicates
}
to check for duplicate elements in a list. However, when there are null items in list this produces a false positive. I can do this using some sluggish code but is there a way to check for duplicates in a list while disregarding null values with a concise way ?
If you're worried about performance, the following code will stop as soon as it finds the first duplicate item - all the other solutions so far require the whole input to be iterated at least once.
var hashset = new HashSet<string>();
if (myList.Where(s => s != null).Any(s => !hashset.Add(s)))
{
// there are duplicates
}
hashset.Add returns false if the item already exists in the set, and Any returns true as soon as the first true value occurs, so this will only search the input as far as the first duplicate.
I'd do this differently:
Given Linq statements will be evaluated lazily, the .Any will short-circuit - meaning you don't have to iterate & count the entire list, if there are duplicates - and as such, should be more efficient.
var dupes = myList
.Where(item => item != null)
.GroupBy(item => item)
.Any(g => g.Count() > 1);
if(dupes)
{
//there are duplicates
}
EDIT: http://pastebin.com/b9reVaJu Some Linqpad benchmarking that seems to conclude GroupBy with Count() is faster
EDIT 2: Rawling's answer below seems at least 5x faster than this approach!
var nonNulls = myList.Where(x => x != null)
if (nonNulls.Count() != nonNulls.Distinct().Count())
{
// there are duplicates
}
Well, two nulls are duplicates, aren't they?
Anyway, compare the list without nulls:
var denullified = myList.Where(l => l != null);
if(denullified.Count() != denullified.Distinct().Count()) ...
EDIT my first attempt sucks because it is not deferred.
instead,
var duplicates = myList
.Where(item => item != null)
.GroupBy(item => item)
.Any(g => g.Skip(1).Any());
poorer implementation deleted.

EF - Can not delete an object

Why does this code not work? It inserts an object but does not delete it
public int Toggle(RequestArchive RequestArchiveObj)
{
var ra = DataContext.RequestArchives.Where(rec => rec.UserId == RequestArchiveObj.UserId && rec.RequestId == RequestArchiveObj.RequestId);
if(ra.Count() > 0)
{
foreach (var item in ra)
{
DataContext.DeleteObject(item);
}
}
else
{
DataContext.AddToRequestArchives(RequestArchiveObj);
}
DataContext.SaveChanges();
return RequestArchiveObj.Id;
}
There's a potentially dangerous issue with your code and your problem might relate to that:
If you loop through your query object (the object returned by DataContext.RequestArchives.Where()) without executing it, you're going to have a roundtrip to the database for every single item in the loop. This is called the N+1 selects problem.
You can mitigate this using the ToList() method:
var ra = DataContext.RequestArchives
.Where(rec =>
rec.UserId == RequestArchiveObj.UserId &&
rec.RequestId == RequestArchiveObj.RequestId)
.ToList(); // this executes the query
// ...
foreach (var item in ra) // without ToList() this will query every item by itself
{
DataContext.DeleteObject(item); // and this might collide with the query
}
I'm not sure about this, but maybe the deletion problem occurs because you try to delete objects while still querying them through the foreach loop. If that is the case, it should work once you're using ToList() as recommended above.

efficient remove of row from dataset linked to SQL database

Hello everone
I'm removing rows from a dataset. All rows that I want to delete have in common that the column have the same value, hence the .FirstOrDefault(x => x.stock == key) which is an int by the way.
public bool RemoveStock(string tickerName) {
bool couldBeRemoved = false;
int key = this.getKeyFromtickerName(tickerName);
stockDataSet.ListingRow found =
listingDataTable.FirstOrDefault(x => x.stock == key);
while (found != null) {
listingDataTable.RemoveListingRow(found);
found = listingDataTable.FirstOrDefault(x => x.stock == key);
}
listingTa.Update(listingDataTable);
listingDataTable.AcceptChanges();
return couldBeRemoved;
}
edit The time is spent in the loop. I assume that the function .FirstOrDefault starts from the beginning of the dataset and I have around 2.5 milion rows, if I remember correctly. end edit
The function works, but painfully slow. It take an order of 10 - 15 minutes to remove 7000 rows. It has to be a better way but how?
Best regards
Gorgen
You are iterating all the records from the start with your while loop. You could just iterate it once to find the records that need to be removed and then you can remove them in a loop like so:
var itemsToBeRemoved = listingDataTable.Where(x=>x.stock == key).ToList();
foreach (var item in itemsToBeRemoved)
listingDataTable.RemoveListingRow(item);

Categories

Resources