Silverlight CSLA 4.2 - BusinessRules.CheckRules throwing Collection Modified exception

Silverlight CSLA 4.2 - BusinessRules.CheckRules throwing Collection Modified exception

Old forum URL: forums.lhotka.net/forums/t/10997.aspx


bwebber posted on Monday, December 19, 2011

I am running into an intermittent problem where 9 times out of 10 everything works fine, but that 10th time I get this weird exception.

The very first object loaded after authentication is the User object.  The constructor for my User object calls the BusinessRules.CheckRules method.

Occasionally this call throws an InvalidOperation exception "Collection was modified; enumeration operation may not execute.".  While I am quite familiar with this error I have no idea how to solve it in this scenario.

The User class only has the Required and StringLength Data Annotation rules.  The exception is raised from Server Side code.

Any idea what may be causing this?  

Is it bad practice to call the CheckRules in the constructor?  The only reason I have this is to validate my default data (all specified within the class, i.e. not in DataPortal_Create).  I could change this to only run this code in Silverlight but then this check would not be done on other platforms.

JonnyBee replied on Monday, December 19, 2011

Do you have a stack trace for the exception and could you show us some code that causes the exception?

ajj3085 replied on Tuesday, December 20, 2011

You should not be doing CheckRules in the constructor.  The normal Csla pattern you won't be using a constructor (directly) at all.  Instead use DataPortal.Create to create your BO or DataPortal.CreateChild for a child BO.  In your DataPortal_Create or Child_Create you may safely call CheckRules.  Csla BOs should have private constructors.

bwebber replied on Wednesday, December 21, 2011

Thanks for the quick responses.

JonnyBee: I tried to replicate the error again yesterday but (thanks to Murphy) I could not.  Though, I was working in SQL most of the day.  When it happens again I will record all the info and post.

Andy: Though this problem is not occurring as a result of a pure create, but rather a fetch, your post raises a concern for me.  Maybe I am not 100% understanding the DataPortal process.  We have been using CSLA successfully since version 2 and have never made use of the DataPortal.Create methods as my understanding was that these methods were useful for populating default values from the data source, where we were quite happy to just use hard coded values instead of incurring the extra server hit to obtain defaults.  In Silverlight I perceived this to be even more important as, according to my understanding, any DataPortal.X call will ultimately result in serialization, post back to server, deserialization on the server, perform the DataPortal_X method, then serialize and return to client which would deserialize to use the object.  This seems like a lot of work to merely create a new object, which I want to happen entirely in the Silverlight client, for this reason I have implemented my create / constructor methods as follows:

We are still using VB, but I'm sure your clever people have no trouble reading both ;) 

 

#If SILVERLIGHT Then

     Public Shared Function NewUser() As User

       Return New User()

     End Function

 #End Region

 

     Public Sub New()

       MarkAsChild()

       BusinessRules.CheckRules()

     End Sub

 

If this is not recommended, I would be interested to know the correct approach to accomplish pure client side creation.

The error is happening during a DataPortal.Fetch of the UserList class.

UserList code (called by DataPortal_Fetch):

    Private Sub Fetch(ByVal sdr As SafeDataReader)

 

      Me.RaiseListChangedEvents = False

      While sdr.Read

        Me.Add(User.GetUser(sdr))

      End While

      Me.RaiseListChangedEvents = True

    End Sub

 

 

User code:

    Friend Shared Function GetUser(ByVal dr As SafeDataReader) As User

      Dim u As New User()

      u.Fetch(dr)

      Return u

    End Function

 

The first line in the GetUser method invokes the constructor above, which occasionally results in the "Collection Modified" error during the BusinessRule.CheckRules call.

JonnyBee replied on Wednesday, December 21, 2011

For Silverlight the constructor must public (so we tend to use public constructors everywhere now).

You _can_  use the [RunLocal] attribute on the DataPortal_Create (or any DataPortal_XYZ or ObjectFactory method)  to instruct the DataPortal to run this code inline and not send call to server.

So I would typically:

You should be aware that your DataAccess may be slowed down by executing rules in both constructor and Fetch.
I assume that you also call CheckRules in Fetch?

bwebber replied on Wednesday, December 28, 2011

Hi JonnyBee

You are right, I am running the CheckRules twice for objects fetched from the database.  Once during object construction and the other after loading the data.  This is inefficient and I will work to eliminate this by using your suggestions.

Question: In terms of your suggestions, we mainly work with editable lists and so the list is responsible for adding new items.  With the latest version of CSLA I no longer override the AddNewCore and leave CSLA to take care of it for me.  Should I now override the AddNewCore to call my Shared NewUser method so that the DataPortal.Create is invoked for new objects added to the list?  I have added breakpoints to this code and CSLA does not seem to call them automatically.

I did manage to replicate that "Collection was modified" error this morning, but I'd expect I could resolve this error by implementing your recommendations above.  Thanks for the help.

This is the stack trace, but it doesn't really give us any more information:

  [External Code]

> Singular.Silverlight.DLL!Singular.Security.User.New() Line 227 + 0x17 bytes

  Singular.Silverlight.DLL!Singular.Security.User.GetUser(Csla.Data.SafeDataReader dr) Line 256 + 0x15 bytes

  Singular.Silverlight.DLL!Singular.Security.UserList.Fetch(Csla.Data.SafeDataReader sdr) Line 139 + 0xe bytes

  Singular.Silverlight.DLL!Singular.Security.UserList.DataPortal_Fetch(Object criteria) Line 180 + 0xd bytes

  [External Code]

JonnyBee replied on Wednesday, December 28, 2011

Hi,

The default AddNewCore executes this code (in SL) :

    protected override void  AddNewCore()
    {
      var item = DataPortal.CreateChild<C>();
      Add(item);
      OnAddedNew(item);
    }

In a BusinessListBase all the items that is added must be child objects so the default behavior is to call DataPortal_CreateChild() method.
IE: The default behavior in an n-tier application is to create the new items locally by calling DataPortal_CreateChild() method..

You can override this code in your own intermediate base class to always call DataPortal.Create or just override in specific classes where you need to access a database to set default values. 

Yes, that stack trace is not very helpful in identifying the issue.

I'm just puzzled that User.New appears to call User.GetUser and UserList.Fetch. This issue may be totally unrelated to business rules.

g.beraudo@castsoftware.com replied on Friday, February 03, 2012

Hi,

I faced the exact same problem with WPF code. Unfortunately, we already uses DataPortal.Create() as follows in our code

        Public Shared Function NewEntity() As T
            Return DataPortal.Create(Of T)()
        End Function

(from: MyCompany.BusinessLayer.dll!MyCompany.BusinessLayer.Objects.ICBusinessBase(Of MyCompany.BusinessLayer.Objects.Contact, MyCompany.BusinessLayer.Objects.Contacts).NewEntity() L )

The stack trace is:

>    Csla.dll!Csla.Rules.BusinessRules.RunRules(System.Collections.Generic.IEnumerable<Csla.Rules.IBusinessRule> rules) Line 490 + 0x80e bytes    C#
     Csla.dll!Csla.Rules.BusinessRules.CheckRulesForProperty(Csla.Core.IPropertyInfo property, bool cascade) Line 456 + 0x14 bytes    C#
     Csla.dll!Csla.Rules.BusinessRules.CheckRules(Csla.Core.IPropertyInfo property) Line 437 + 0x14 bytes    C#
     Csla.dll!Csla.Rules.BusinessRules.CheckRules() Line 384 + 0x12 bytes    C#
     Csla.dll!Csla.Core.BusinessBase.DataPortal_Create() Line 1147 + 0x16 bytes    C#
     [Lightweight Function]   
     Csla.dll!Csla.Reflection.MethodCaller.CallMethod(object obj, Csla.Reflection.DynamicMethodHandle methodHandle, object[] parameters) Line 545 + 0x1e bytes    C#
     Csla.dll!Csla.Reflection.MethodCaller.CallMethod(object obj, string method, object[] parameters) Line 413 + 0xf bytes    C#
     Csla.dll!Csla.Reflection.LateBoundObject.CallMethod(string method) Line 77 + 0x2e bytes    C#
     Csla.dll!Csla.Server.SimpleDataPortal.Create(System.Type objectType, object criteria, Csla.Server.DataPortalContext context) Line 58 + 0x11 bytes    C#
     Csla.dll!Csla.Server.DataPortalSelector.Create(System.Type objectType, object criteria, Csla.Server.DataPortalContext context) Line 41 + 0x14 bytes    C#
     Csla.dll!Csla.Server.DataPortal.Create(System.Type objectType, object criteria, Csla.Server.DataPortalContext context) Line 132 + 0x12 bytes    C#
     Csla.dll!Csla.DataPortalClient.LocalProxy.Create(System.Type objectType, object criteria, Csla.Server.DataPortalContext context) Line 35 + 0x1b bytes    C#
     Csla.dll!Csla.DataPortal.Create(System.Type objectType, object criteria) Line 136 + 0x12 bytes    C#
     Csla.dll!Csla.DataPortal.Create<CAST.InfoCAST.BusinessLayer.Objects.Contact>() Line 94 + 0x43 bytes    C#
     MyCompany.BusinessLayer.dll!MyCompany.BusinessLayer.Objects.ICBusinessBase(Of MyCompany.BusinessLayer.Objects.Contact, MyCompany.BusinessLayer.Objects.Contacts).NewEntity() Line 80 + 0x2f bytes    Basic

We are migrating our UI from winform to WPF (& Silverlight also).

 

The exact same code do work with WinForm. Switching to the WpfForm shows the problem.

On my  WPF form, I have 2 users controls, if I remove one of them, then I do not have the problem on Wpf neither.

I'm suspecting that the origin of the trouble comes from mutli-threading stuffs called by the databinding... if anyone has clues on how to investigate the problem further, I would be more than interested. I'm using CSLA 4.1.0.

 

JonnyBee replied on Friday, February 03, 2012

Can you provide us with a small sample to reproduce the issue? 

This would help greatly in identifying the issue.

 

JonnyBee replied on Sunday, February 05, 2012

Could you try and upgrade you solution to use CSLA 4.3.0 Alpha and check if the issue still exists?

BrokenRulesCollection has been updated to avoid possible thread race conditions in ClearRules and SetBrokenRules methods.

 

g.beraudo@castsoftware.com replied on Monday, February 06, 2012

Hi Jonny,

Unfortunately, CSLA 4.3.0 does not fix the problem (but fixed another of mine, so the try was worth the time).

My application is quite complex, and the CSLA entity a little too much related to everything else (this was inherited from past, before we started using CSLA and clean principle). If I succeed, I will share it.

Gilles

Copyright (c) Marimer LLC