5

I am receiving following error and this error only comes up when multiple users are hitting the same button. Any help/ideas will be really appreciated:

System.InvalidOperationException: Collection was modified; enumeration operation may not execute. Generated: Wed, 10 Jun 2015 07:29:06 GMT

AutoMapper.AutoMapperMappingException:

Mapping types: User -> User ApplicationSecurityManager.Service.User -> ApplicationSecurityManager.Models.User

Destination path: User

Source value: ApplicationSecurityManager.Service.User ---> System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at System.Collections.Generic.List1.Enumerator.MoveNextRare() at AutoMapper.TypeMap.<get_AfterMap>b__1(Object src, Object dest) at AutoMapper.Mappers.TypeMapObjectMapperRegistry.PropertyMapMappingStrategy.Map(ResolutionContext context, IMappingEngineRunner mapper) at AutoMapper.Mappers.TypeMapMapper.Map(ResolutionContext context, IMappingEngineRunner mapper) at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context) --- End of inner exception stack trace --- at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context) at AutoMapper.MappingEngine.Map[TDestination](Object source, Action1 opts) at ApplicationSecurityManager.UserManager.LoadUser(String username) at ApplicationSecurityManager.UserManager.get_AuthenticatedUser() at ApplicationSecurityManager.UserManager.IsAuthenticated() at ApplicationSecurityManager.Infrastructure.ApplicationSecurityAttribute.OnAuthorization(AuthorizationContext filterContext) at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList1 filters, ActionDescriptor actionDescriptor) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult1.Begin(AsyncCallback callback, Object state, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate1 endDelegate, Object tag, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate1 endDelegate, Object tag) at System.Web.Mvc.Controller.<>c__DisplayClass1d.b__17(AsyncCallback asyncCallback, Object asyncState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult1.Begin(AsyncCallback callback, Object state, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate1 endDelegate, Object tag, Int32 timeout) at System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult1.Begin(AsyncCallback callback, Object state, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate1 endDelegate, Object tag, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, Object tag) at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) at System.Web.Mvc.MvcHandler.<>c__DisplayClass8.b__2(AsyncCallback asyncCallback, Object asyncState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult1.Begin(AsyncCallback callback, Object state, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate1 endDelegate, Object tag, Int32 timeout) at System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, Object tag) at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

This is the constructor where i think the aftermap is the problem but when debugging i don't receive the error.

Public Sub New(environmentCode As String, applicationCode As String)
    MyBase.New(environmentCode, applicationCode)

    SOBaseUrl = System.Configuration.ConfigurationManager.AppSettings(Enums.AppSettingKeys.SOBaseUrl.ToString())
    If Not String.IsNullOrEmpty(SOBaseUrl) Then
        SOBaseUrl = SOBaseUrl.TrimEnd("/")
    End If

    'Setup mapping.
    Mapper.CreateMap(Of Service.User, Models.User)() _
        .ForMember(Function(dest As Models.User) dest.ENumber, Sub(opt) opt.MapFrom(Function(src As Service.User) src.INumber)) _
        .AfterMap(Sub(src As Service.User, dest As Models.User)

            dest.Groups = New List(Of String)

            Using service = ApplicationSecurityManager.Service.Factory.GetService()

                Dim applicationPermissions = service.LoadPermissionsForUser(dest.Username, MyBase.EnvironmentCode)

                If (Not applicationPermissions Is Nothing AndAlso applicationPermissions.Any(Function(x) x.Code = MyBase.ApplicationCode)) Then

                    dest.Groups = applicationPermissions.Single(Function(x) x.Code = MyBase.ApplicationCode).GroupNames.ToList()

                End If

            End Using

        End Sub)

Depenendency Injection Mapping:

container.RegisterType(Of IUserManager, UserManager)(New PerThreadLifetimeManager(),
    New InjectionConstructor(
      ConfigurationManager.AppSettings(Common.Enums.AppSettingKeys.Environment.ToString()),
      ConfigurationManager.AppSettings(Common.Enums.AppSettingKeys.ApplicationCode.ToString()))
    )

In the saveUserResponse, the error is getting thrown.

 Public Function Create(user As Common.Models.User, approve As Boolean) As SO.Common.Models.User Implements IUserProvider.Save

    Dim saveUserResponse = UserManager.SaveUser(Mapper.Map(Of ApplicationSecurityManager.Service.User)(user))

    If Not String.IsNullOrEmpty(saveUserResponse.ErrorMessage) Then

        'The Security system returned an error.

        Throw New Exception("Security Service returned error: " & saveUserResponse.ErrorMessage)

    End If

    'Return the username.
    Return Mapper.Map(Of Common.Models.User)(saveUserResponse.User)

End Function
Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
Baahubali
  • 4,604
  • 6
  • 33
  • 72

2 Answers2

1

This is pretty common mistake modifying a collection while iterating it. Is it possible you are leaving something out here we can't see? Anyway's in the code that you have provided there is not any such scenario from what I can see. This means that you might be calling this in a multi-threaded environment and the collection is being modified in some other thread.

What can you try?

Look into locking your enumeration so only one thread at a time can be accessed. It's possible that it's being accessed multiple time's when user's are clicking on the button.

Here's a good link to help you out: http://weblogs.asp.net/leftslipper/mvc-locking-the-routecollection

Trevor
  • 7,777
  • 6
  • 31
  • 50
  • i thought because Automapper.CreateMap and AfterMap functions are getting called in a constructor of the class. if some user is trying to save the object by calling function create as listed above and some other user creates a new object which would call the Automapper to modify the collection by calling the Aftermap function that would throw the collection has been modified error? i even tried putting the code in synclock but the error does not change. i was thinking of refactoring the code but wanted to completely understand how does the Aftermap function works. – Baahubali Jun 16 '15 at 01:39
  • @user1490835 the `Aftermap` runs only once per mapping. If you want to run it only once, you should attach it to a List to List mapping configuration and not an item to item configuration; this would definitely throw a `collection has been modified error`... – Trevor Jun 16 '15 at 15:08
  • sorry what do you mean by list to list mapping and not an item to item configuration? – Baahubali Jun 17 '15 at 04:45
  • also so does AfterMap function gets called everytime a mapping is defined or is it actually called when Mapping.Map is called? – Baahubali Jun 17 '15 at 07:23
1

This is caused when multiple users modify the same user.Groups collection in the AfterMap() method of your mapping. To avoid this, lock the code that is processing the collection. For example:

'class level object
private object _myLock = New object()

'Setup mapping.
Mapper.CreateMap(Of Service.User, Models.User)() _
    .ForMember(Function(dest As Models.User) dest.ENumber, Sub(opt) opt.MapFrom(Function(src As Service.User) src.INumber)) _
    .AfterMap(Sub(src As Service.User, dest As Models.User)
                  SyncLock _myLock
                      dest.Groups = New List(Of String)

                      Using service = ApplicationSecurityManager.Service.Factory.GetService()

                          Dim applicationPermissions = service.LoadPermissionsForUser(dest.Username, MyBase.EnvironmentCode)

                          If (Not applicationPermissions Is Nothing AndAlso applicationPermissions.Any(Function(x) x.Code = MyBase.ApplicationCode)) Then

                              dest.Groups = applicationPermissions.Single(Function(x) x.Code = MyBase.ApplicationCode).GroupNames.ToList()

                          End If

                      End Using
                  End SyncLock

              End Sub)
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68
  • i did perform this step but a little different to what you said: synclock _mylock Mapper.CreateMap(Of Service.User, Models.User)() _ .ForMember(Function(dest As Models.User) dest.ENumber, Sub(opt) opt.MapFrom(Function(src As Service.User) src.INumber)) _ .AfterMap(Sub(src As Service.User, dest As Models.User) ..code End Sub) endsynclock. so i put the whole thing in synclock instead of just the group code. do you think that's where the problem was? – Baahubali Jun 18 '15 at 01:51
  • 1
    Two things. Remember that the AfterMap() method is not called when you call CreateMap(), but after you call Mapper.Map() and it invokes the mapping that you have configured. So the SyncLock will not be effective your way. Secondly, you should always lock as little code as possible. It is a collection on the user object that is complaining during AfterMap, according to the stack trace - which points to .Groups. Therefore, lock the processing around that collection only. – Paul Taylor Jun 18 '15 at 08:33
  • Thank you for explaining that. – Baahubali Jun 19 '15 at 01:23