Print Page | Close Window

Collection was modified; enumeration operation may not execute.

Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=2359
Printed Date: 29-Jul-2026 at 10:05am


Topic: Collection was modified; enumeration operation may not execute.
Posted By: bennage
Subject: Collection was modified; enumeration operation may not execute.
Date Posted: 09-Dec-2010 at 11:55am
We have a Silverlight 4 application. A user can open multiple records for editing and tab between them. We create a child entity manager for each screen, in order to isolate changes until we are ready to commit the master entity manager. When the user wants to save changes on an individual screen, we execute the following:

Manager.SaveChangesAsync(SaveCompleted);

Where Manager is the child entity manager specific to that screen. In SaveCompleted, we check for errors and then merge the child back into the master like this:

Manager.CacheStateManager.GetCacheState()
     .Merge(Context.Entities, RestoreStrategy.Normal);

All of this seems to work fine, however users periodically hit the following exception:

Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.WhereListIterator`1.MoveNext()
   at IdeaBlade.Core.EnumerableFns.ForEach[T](IEnumerable`1 items, Action`1 action)
   at IdeaBlade.EntityModel.PendingEntityMap.Update(EntityWrapper pendingWrapper, EntityWrapper realWrapper)
   at IdeaBlade.EntityModel.ScalarEntityReference`1.InformPendingEntityOfRealEntity(EntityWrapper realEntity)
   at IdeaBlade.EntityModel.ScalarEntityReference`1.QueryCallback(EntityQueryOperation op)
   at IdeaBlade.EntityModel.BaseOperation`2.OnCompleted()
   at IdeaBlade.EntityModel.BaseOperation`2.IdeaBlade.EntityModel.IHasAsyncEventArgs.OnCompleted()
   at IdeaBlade.EntityModel.AsyncProcessor`1.<>c__DisplayClass2.<.ctor>b__0(TArgs args)
   at IdeaBlade.EntityModel.AsyncProcessor`1.Signal()
   at IdeaBlade.EntityModel.AsyncProcessor`1.<Execute>b__5(Object x)

This exception is not consistent, however it is eventual. I mean, it doesn't happen on every save, but it happens eventually when a user is working on a set of records. It seems to be happening between the 3rd and 10th save operation.

My first question is: how can I get more information on the context of this exception? I don't even know which collection is being modified.



Replies:
Posted By: bennage
Date Posted: 09-Dec-2010 at 1:34pm
It turns out that the exception was not directly related to save operation. After a user modified a record, they would then search for the next record my name. We'd present a screen with the search results, the user would chose one of the results and begin working.
The problem occurred when the results contained a record that had been just been edited. ("Just" here is not about timing, but rather in the current session of work.)
The query for new records was performed against the master entity manager. We modified it to use a child entity manager and the exception went away.


Posted By: WardBell
Date Posted: 09-Dec-2010 at 6:30pm
@bennage - An intriguing chain of events.
 
I couldn't help noticing that you were swinging a very big hammer when you merged the entire contents of the child manager into the main manager. It's also a good thing that you aren't deleting in the child manager because the entities deleted in the child manager would not have been removed from the main manager.
 
May I suggest an alternative?
 
The EntitySaveOperation (like the EntitySavedEventArgs passed to a Completed event handler) contains an Entities property which contains every entity actually saved*. You can IMPORT just these entities (see EntityManager.ImportEntities) into the main EntityManager.
 
Ah ... but I haven't talked about deletes. The import would be a disaster if you had deleted entities as part of the save ... because you would import deleted entities into the target manager.
 
It seems both the big hammer and the little hammer leave (or put) deleted entities into the main manager. What to do?
 
Answer: The deleted entities among the "Entities" collection will all have the "Detached" EntityState. Thus, you should: 
(a) import only the entities that are "Unchanged" (i.e., every entity in "Entities" except the deleted ones) and
(b) communicate the entity keys of the "Detached" entities (the deleted ones) to the caller so it can remove the corresponding entities from the main EntityManager's cache.
 
Almost needless to say, I hide these mechanics in helper classes.
 
Bonus: Manager-to-Manager importing and exporting works great when the two managers (wrapped, I hope, in repository classes) should conversed directly with each other.
 
In many applications, the repository that saves has no idea who is interested in the saved results. This is a perfect case for some kind of message using the EventAggregator pattern. The saving Repository sends an "EntitiesSaved" message whose payload includes the contents of the "Entities" properties. Message receivers (which should be Repositories in my opinion) decide if any of those entities are interesting and do whatever they should do.
 
All of this looks to be fodder for a future cookbook example.
 
--
 
* It is possible to tell the server NOT to send back certain entities. The ones you filter OUT in this manner would not appear in the Entities property ... for obvious reasons. I assume you have not availed yourself of this highly specialized feature. Let me know if you have done so  ... or want to do so ... and I'll explain both how to do that and also how to remember to tell interested listeners about the entities that weren't returned to the client.


Posted By: WardBell
Date Posted: 09-Dec-2010 at 6:45pm
@bennage -
 
I assume that you are not using concurrency checking. Otherwise you'd be reporting concurrency errors when users started to work on an "old" copy of an entity that had not been returned from the server and merged back into cache.
 
It takes a user doing just the wrong thing very quickly to get caught in this race condition. You can narrow that window further by creating your own RestoreStrategy with a MergeStrategy of "OverwriteChanges" (you specify that MergeStrategy in the ImportEntities call if you go that route).
 
Of course the user might see the following sequence
  • Modify Nancy Davolio to "Suzie" Davolio and save
  • Immediately select Nancy Davolio again (her name hasn't been updated yet) and change her name to "Betty"
  • The save completes and the saved entity is merged back into the main EntityManager
  • The screen jumps suddenly and "Betty" becomes "Suzie" as the "OverwriteChanges" takes effect and the PropertyChanged event fires.

It's still possible with a really slow save to do the following

  • Modify Nancy Davolio to "Suzie" Davolio and save
  • Immediately select Nancy Davolio again (her name hasn't been updated yet) and change her name to "Betty"
  • Try to save "Betty"
  • Concurrency error raised because "Suzie" is now in the database.
I'm sure you can imagine permutations.
 
The way to think about this is that each EntityManager is a different user. You either go the out-of-the-box concurrency strategy of last-one-wins or you turn on optimistic concurrency and start handling the inevitable errors.
 
Note to interested readers: I mean "inevitable". There is no magic here. DevForce can't know what your resolution strategy is or should be. Concurrency resolution is a professional programmers problem.
 
Let's face it: 9 out of 10 applications go with last-one-wins ... in which case you can only strive to reduce the likelihood of a surprise: with Nancy become Suzie or Betty ;->  ?
 
 



Print Page | Close Window