John John
John John

Reputation: 1

Inconsitency error inside Entity Framework , "Collection was modified; enumeration operation may not execute."

I have the following action method inside my asp.net mvc web application:-

[HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Dept dept = db.Depts.Include(a => a.Emps).Single(a=>a.DeptID == id);

            var emps = dept.Emps;
            foreach (var c in emps)
            {
                dept.Emps.Remove(c);

            }
          db.Depts.Remove(dept);
            db.SaveChanges();
            return RedirectToAction("Index");
        } 

now this will raise the following exception , inside the foreach code :-

An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code

Additional information: Collection was modified; enumeration operation may not execute.

but if i simply add a .ToList() to the emps as follow:-

var emps = dept.Emps.ToList();
                foreach (var c in emps)
                {

the error will disappear. but my question is why i need to add a .ToList() ? since in my case the dept variable should have the related emps records from the database , because i have added a .Include(), so the .Tolist() which will force the error to disappear should not have any effect.. ? Can anyone advice on this please? Thanks

Upvotes: 0

Views: 1679

Answers (1)

Erik Funkenbusch
Erik Funkenbusch

Reputation: 93424

Let's say you have a bunch of pennies you're counting, one by one...

1, 2, 3, 4... and you get about halfway through, and then someone comes along and reshuffles them all up on you.. and you lose count.

That's exactly what happens when you call Remove while you are in a foreach. The foreach "loses track" of the collection because you removed an item while it was in the middle of enumerating it.

When you add a .ToList(), it creates a copy of the collection in a List object in memory, which you are enumerating.. so when you call remove on the original dept.Emps, there is no conflict, since you are not enumerating that collection. You're enumerating the copy. Your copy still has the item you removed, but the DataContext now does not.

In the example, you have two identical sets of pennies (the original and a copy), you start counting one set... and someone comes along and messes up the second set, but it doesn't matter to you because your set is still just fine, you can keep going.

This has nothing to do with using Include. You are confusing using .ToList() to execute a query with using .ToList() to make a copy of the data.

EDIT:

The simple fact is that you cannot modify a collection while you are enumerating it. As soon as you modify it, by calling Remove or Insert or anything else, the enumerator becomes invalid and continuing your foreach throws an exception.

Calling ToList() creates a copy of the original collection, which allows you to enumerate the copy, while you modify the original. Therefore, the enumerator of your copy does not become invalidated.

It doesn't matter that this is a DataContext, it works the same with any kind of collection. Lists, DbSets, Dictionaries, HashSets, whatever.. if you enumerate something, you cannot modify while you are enumerating it without invalidating the enumerator.

You will have the exact same problem with this code:

List<int> myList = new List<int>{1, 2, 3};

foreach(int x in myList)
{
    if (x == 2)
        myList.Remove(x);
}

You will have to do this, since .ToList() creates a copy of the original List:

List<int> myList = new List<int>{1, 2, 3};

foreach(int x in myList.ToList()) // Even though myList is already a list
{
    if (x == 2)
        myList.Remove(x);
}

The point here is that this problem has nothing to do with databases, or includes, or Single() or anything else... it's strictly a problem with modify a collection while you are enumerating it. That's it. That's all it is, and nothing more.

Upvotes: 5

Related Questions