Is this to do with csla?

Is this to do with csla?

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


Nemisis posted on Friday, November 27, 2009

Hi all, i am getting this error in my application when people try to edit a business object.  I am using version 3.6.1 with .net 3.5.  The stacktrace would suggest that the problem would lye within CanReadProperty, but when i look up this error online, people say that mscor.lib is something to do with user code not csla?  We have many business objects, some of which have the same property names, which shouldnt be a problem.  We are using managed fields as well.

Anyone got any clues?

Message : An item with the same key has already been added.
StackTrace : at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at Csla.Core.BusinessBase.CanReadProperty(String propertyName)
at Csla.Core.BusinessBase.CanReadProperty(String propertyName, Boolean throwOnFalse)
at Csla.Core.BusinessBase.GetProperty[P](PropertyInfo`1 propertyInfo, NoAccessBehavior noAccess)
at Csla.Core.BusinessBase.GetProperty[P](PropertyInfo`1 propertyInfo)
at BSL.Library.Configuration.Module.Module.get_ClassName()
at BSL.Library.Configuration.Module.ModuleList.get_GetModule(String pName)
at BSL.Library.Core.CompanyItem.CanGetObject()
at BSL.Web.eContrack.Page_Load(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

ajj3085 replied on Monday, November 30, 2009

If you're registerpropety calls are not setup correctly, this may happen.  Are you using the overloads which take a System.Type as the first parameter?  If you are, don't.

Nemisis replied on Thursday, December 17, 2009

Hi,

No i am not using that overload, i do not pass the System.Type

Here is an example of one of my properties

        Private Shared NameProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Name"))
        Public Property Name() As String
            Get
                Return GetProperty(NameProperty)
            End Get
            Set(ByVal value As String)
                SetProperty(NameProperty, value)
            End Set
        End Property

The stacktrace seems to imply that the error is being caused within the CanReadProperty? The code for the CanReadProperty(propertyName) method.  If i load this in BusinessBase (which is what my object inherits from) you will see

    public virtual bool CanReadProperty(string propertyName)
    {
      bool result = true;

      VerifyAuthorizationCache();

      if (!_readResultCache.TryGetValue(propertyName, out result))
      {
        result = true;
        if (AuthorizationRules.HasReadAllowedRoles(propertyName))
        {
          // some users are explicitly granted read access
          // in which case all other users are denied
          if (!AuthorizationRules.IsReadAllowed(propertyName))
            result = false;
        }
        else if (AuthorizationRules.HasReadDeniedRoles(propertyName))
        {
          // some users are explicitly denied read access
          if (AuthorizationRules.IsReadDenied(propertyName))
            result = false;
        }
        // store value in cache
        _readResultCache.Add(propertyName, result);
      }
     
      return result;
    }

I have searched to posts about this on the forum and found this http://forums.lhotka.net/forums/post/19091.aspx

The forum is to do with another method called "SharedAuthorisationRules.GetManager()" but i wonder if the problem is happening here.  Our servers are multi-core and if two cores try to execute

if (!_readResultCache.TryGetValue(propertyName, out result))

and they are both false, then both will execute

_readResultCache.Add(propertyName, result);

which i believe is where the error is.  Shouldnt this all be wrapped in a lock?

ajj3085 replied on Thursday, December 17, 2009

Is your class subclassing another class which has its own property info objects?

Nemisis replied on Thursday, December 17, 2009

no not this one

ajj3085 replied on Thursday, December 17, 2009

Hmm... I'm at a loss.  Can you post all of the classes' code?

Nemisis replied on Friday, December 18, 2009

Its all very simple, i am using a lasy load as one property, which is below, but i guess there is nothing wrong with this is there?

The stacktrace suggests that there maybe a problem
Normal 0 false false false EN-GB X-NONE X-NONE MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable {mso-style-name:"Table Normal"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi;} Message : An item with the same key has already been added.
StackTrace : at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at Csla.Core.BusinessBase.CanReadProperty(String propertyName)
at Csla.Core.BusinessBase.CanReadProperty(String propertyName, Boolean throwOnFalse)
at Csla.Core.BusinessBase.GetProperty[P](PropertyInfo`1 propertyInfo, NoAccessBehavior noAccess)
at Csla.Core.BusinessBase.GetProperty[P](PropertyInfo`1 propertyInfo)
at BSL.Library.Configuration.Database.get_ModuleList()
at BSL.Library.Scoring.ScorecardItem.CanGetObject()
at BSL.Web.ScoringDataEntry.ScoringWizard.Page_Load(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


Namespace Configuration
    <Serializable()> _
    Public Class Database
        Inherits BusinessBase(Of Database)

#Region " Business Methods "

#Region " Basic "

        Private Shared SamProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Sam"))
        Public ReadOnly Property Sam() As String
            Get
                Return GetProperty(SamProperty)
            End Get
        End Property

        Private Shared NameProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Name"))
        Public Property Name() As String
            Get
                Return GetProperty(NameProperty)
            End Get
            Set(ByVal value As String)
                SetProperty(NameProperty, value)
            End Set
        End Property

        Private Shared ShortNameProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("ShortName"))
        Public Property ShortName() As String
            Get
                Return GetProperty(ShortNameProperty)
            End Get
            Set(ByVal value As String)
                SetProperty(ShortNameProperty, value)
            End Set
        End Property

        Private Shared ImageProperty As PropertyInfo(Of Byte()) = RegisterProperty(New PropertyInfo(Of Byte())("Image"))
        Public Property Image() As Byte()
            Get
                Return GetProperty(ImageProperty)
            End Get
            Set(ByVal value As Byte())
                SetProperty(ImageProperty, value)
            End Set
        End Property

        Private Shared ConfigurationProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Configuration"))
        Public Property Configuration() As String
            Get
                Return GetProperty(ConfigurationProperty)
            End Get
            Set(ByVal value As String)
                SetProperty(ConfigurationProperty, value)
            End Set
        End Property

        Private Shared ThemeProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Theme"))
        Public Property Theme() As String
            Get
                Return GetProperty(ThemeProperty)
            End Get
            Set(ByVal value As String)
                SetProperty(ThemeProperty, value)
            End Set
        End Property

        Private Shared LocaleProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Locale"))
        Public Property Locale() As String
            Get
                Return GetProperty(LocaleProperty)
            End Get
            Set(ByVal value As String)
                SetProperty(LocaleProperty, value)
            End Set
        End Property

        Private Shared ModuleListProperty As PropertyInfo(Of ModuleList) = RegisterProperty(New PropertyInfo(Of ModuleList)("ModuleList"))
        Public ReadOnly Property ModuleList() As ModuleList
            Get
                If Not FieldManager.FieldExists(ModuleListProperty) Then
                    LoadProperty(ModuleListProperty, ModuleList.GetModuleList(ReadProperty(SamProperty)))
                End If
                Return GetProperty(ModuleListProperty)
            End Get
        End Property

#End Region

        ''' <summary>
        ''' Used this function to find a name.  You must pass in the fullname, module and property name separated by a full stop.  e.g. CompanyItem.Name
        ''' This function must be used to return a property within a module!
        ''' </summary>
        ''' <param name="pName"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function GetName(ByVal pName As String) As Name.Name

            For Each lModule As [Module].Module In ModuleList

                If pName.Contains(".") AndAlso lModule.ClassName = pName.Substring(0, pName.IndexOf(".")) Then

                    Return lModule.NameGroupList.GetName(pName.Substring(pName.IndexOf(".") + 1))

               End If

            Next

            Throw New ArgumentException(pName & " does not match or contain the name of any module within this database.")

        End Function

#End Region

#Region " Factory Methods "

        Public Shared Function NewDatabase() As Database
            Return DataPortal.Create(Of Database)()
        End Function

        Public Shared Function GetDatabase(ByVal pName As String) As Database
            Return DataPortal.Fetch(Of Database)(New SingleCriteria(Of Database, String)(pName))
        End Function

        Private Sub New()
            'Require use of factory methods
        End Sub

#End Region

#Region " Data Access "

        <RunLocal()> _
        Protected Overrides Sub DataPortal_Create()
            MyBase.DataPortal_Create()
        End Sub

        Private Overloads Sub DataPortal_Fetch(ByVal criteria As SingleCriteria(Of Database, String))

            Using ctx = ConnectionManager(Of SqlClient.SqlConnection).GetManager(BSL.Library.Configuration.Current.Security)
                Using cm = ctx.Connection.CreateCommand
                     ..............
                End Using
            End Using

        End Sub

        <Transactional(TransactionalTypes.TransactionScope)> _
        Protected Overrides Sub DataPortal_Insert()
            ' insert values
        End Sub

        <Transactional(TransactionalTypes.TransactionScope)> _
        Protected Overrides Sub DataPortal_Update()

            Using ctx = ConnectionManager(Of SqlConnection).GetManager(BSL.Library.Configuration.Current.Data)
                If IsSelfDirty Then
                    Using cm = ctx.Connection.CreateCommand
                       ..............
                    End Using
                End If

                FieldManager.UpdateChildren(Me)
            End Using

        End Sub

        <Transactional(TransactionalTypes.TransactionScope)> _
        Protected Overrides Sub DataPortal_DeleteSelf()

        End Sub

#End Region

    End Class

End Namespace

rasupit replied on Friday, December 18, 2009

Nemisis:
Our servers are multi-core and if two cores try to execute

if (!_readResultCache.TryGetValue(propertyName, out result))

and they are both false, then both will execute

_readResultCache.Add(propertyName, result);

which i believe is where the error is.  Shouldnt this all be wrapped in a lock?


Agree, this is more likely a race condition issue. I ran into this issue when using csla object with multi-threaded process on windows service.  At that time took a shortcut and just remove the CanReadProperty since not really applicable in windows service.

This can be wrapped with lock, but locking will pay performance penalty.   I think the better fix is to just let it override the value when propName already exist since the overriding value will be the same anyway.  So the dictionary add should just look like:

_readResultCache[propertyName] = result;

Ricky

ajj3085 replied on Friday, December 18, 2009

Ricky is correct... either add locking or just always overwrite the value.

Csla isn't threadsafe by default, just like most of the .Net framework.

Nemisis replied on Friday, December 18, 2009

That is a much better suggestion thenthe lock, i didnt even think of that too be honest.  Cheers guys!

Rocky, is there any chance this could be done, in future versions as i dont want to update our copy and then get the same error in a future version?

It appears that this change has been done in the CanWriteProperty

    public virtual bool CanWriteProperty(string propertyName)
    {
      bool result = true;

      VerifyAuthorizationCache();

      if (!_writeResultCache.TryGetValue(propertyName, out result))
      {
        result = true;
        if (this.AuthorizationRules.HasWriteAllowedRoles(propertyName))
        {
          // some users are explicitly granted write access
          // in which case all other users are denied
          if (!AuthorizationRules.IsWriteAllowed(propertyName))
            result = false;
        }
        else if (AuthorizationRules.HasWriteDeniedRoles(propertyName))
        {
          // some users are explicitly denied write access
          if (AuthorizationRules.IsWriteDenied(propertyName))
            result = false;
        }
        _writeResultCache[propertyName] = result;
      }
      return result;
    }

admin replied on Friday, December 18, 2009

Yes, I'd say that the CanReadProperty() behavior is a bug, and it should match CanWriteProperty(). I'll add this to the bug list.

http://www.lhotka.net/cslabugs/edit_bug.aspx?id=670

Copyright (c) Marimer LLC