automapper error collection was modified when multiple users are creating a user

1.7k views Asked by At

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
2

There are 2 answers

2
Paul Taylor On BEST ANSWER

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)
4
Trevor On

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