Property Setter running AFTER PropertyHasChanged

Property Setter running AFTER PropertyHasChanged

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


swegele posted on Wednesday, April 09, 2014

When my private backing field SetProperty is called...even though it is ByRef, the value in mDTO.EvidNum does not change until AFTER the PropertyHasChanged runs.

This means that a simple rule like StringRequired runs and sees an empty string and the rule stays broken.  But the next step after rules running is that the mDTO.EvidNum property setter is called.  So you look at the business object and the rule is broken but there is a value there.

Any ideas?  I assume the CSLA code assumes a private backing field is just a string not a Class.StringProperty.

 

I have the following code:

    Private Shared EvidNumPropertyInfo As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.EvidNum, RelationshipTypes.PrivateField)

    Public Property EvidNum() As String

        Get

            Return GetProperty(EvidNumPropertyInfo, mDTO.EvidNum)

        End Get

        Set(ByVal value As String)

            SetProperty(EvidNumPropertyInfo, mDTO.EvidNum, value)

        End Set

Normal 0 false false false EN-US JA X-NONE

    End Property

 

JonnyBee replied on Wednesday, April 09, 2014

Hi, 

Which version of CSLA do you use? 

I wouldn't be surprised if this has to do with the "mDTO.EvidNum" property.

What is sent in as a ref value to the SetProperty method when the wrapped object expose a property or a field.?
When it is a  property I suspect that what is sent in as ref parameter is not the property method but the "backing field value" of the property. 

Does it work correct if you change mDTO.EvidNum to a private member field in the business object class? 
Does it work correct if you change mDTO.EvidNum to a string Field rather than a property? 

swegele replied on Wednesday, April 09, 2014

Using the latest Beta version 4.5.8

I just did a test and it works as expected if it is a plain string private member backing field  in my business object.  ex. mTesting as string

Here is the property in my DTO

Public Overridable Property [EvidNum]() As System.String

Get

Return CType(GetValue(CInt(EvidenceFieldIndex.EvidNum), True), System.String)

End Get

Set

SetValue(CInt(EvidenceFieldIndex.EvidNum), value)

End Set

 

End Property

This could be nasty...All my DTOs are auto generated and this pattern is ubiquitous in my BOs.  Ugh.

 

JonnyBee replied on Wednesday, April 09, 2014

Hi,

The challenge about properties is that they are actually compiled into 2 methods in the object 

get_EvidNum and set_EvidNum  - it is not represented as a single field/value. 

See also https://msmvps.com/blogs/luisabreu/archive/2007/09/17/properties-on-c.aspx  

I'm sure there is a good reason why this is not supported out of the box as a private backing field in CSLA - tho' I haven't spent any time digging into it. 

You _might_ get away with creating your own intermediate base class and create your own SetProperty2 method without a ref parameter and use f.ex MethodCaller.CallPropertyGetter / MethodCaller.CallPropertySetter to get/set the property values of the DTO. (Or use plain reflection in .NET or look at FasterFlect)

 

 

JonnyBee replied on Thursday, April 10, 2014

Hi, 

Remember, the compiler creates 2 methods in compiled IL code when you create a property in your code: get_<propertyname> and set_<propertyname>

So when you _try_ to pass a property as ref parameter you actually pass the returned value. 

You might want to try and create your own implementation of SetProperty in your own intermediate base class that accepts a property and use reflection to get/set the property value (or Csla.Reflection.MethodCaller or FasterFlect).  

swegele replied on Thursday, April 10, 2014

I'm sorry Jonny, I don't understand what you are saying about my own implementation in a base class.

Your saying by passing in a property, it is being passed ByVal and not ByRef...so it ends up getting Set after the rules run.  You are saying somehow I can use reflection to overcome that?

By the way here is a description of what is going on from a different forum:

When you execute Method(Object.PropertyValue), and PropertyValue is passed ByRef, the method first executes the PropertyValue GET statement to populate the variable which will be used in the method.  If you do not update the object pointer, but only change a member of the parameter variable inside the method, the change to that member's value would be immediately apparent outside the executing method.  But if the variable is reassigned to another pointer, the pointer internal to the original property will not be updated until the SET statement is executed, so you won't see the object change immediately.

Maybe some example code would help:

Public Class Form1
    Dim f As New Fiddle

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Foo(f.Faddle, f.Button)
    End Sub

    Private Sub Foo(ByRef obj As String, ByRef btn As Button)
        obj &= " Changed"
        'btn = New Button
        btn.Text &= " Changed"
    End Sub
End Class

Public Class Fiddle
    Public Sub New()
        mButton = New Button
        mButton.Text = "First"
    End Sub

    Private mFaddle As String = "Fiddle Faddle"
    Public Property Faddle() As String
        Get
            Return mFaddle
        End Get
        Set(ByVal value As String)
            mFaddle = value
        End Set
    End Property

    Private mButton As New Button
    Public Property Button() As Button
        Get
            Return mButton
        End Get
        Set(ByVal value As Button)
            mButton = value
        End Set
    End Property
End Class

Place a breakpoint on Sub Foo() then run the form.  First, note the values of Faddle and Button as listed on the 'f' variable declaration.  Now step through sub Foo() and stop at 'End Sub'.  Notice that 'f.Button.Text' has changed, however 'f.Faddle' has not yet changed.  Continue to step through the code.  Note how execution returns to the executing method, then to each property SET statement, then back to the method.  Immediately after the Faddle SET statement and execution returns to the method, you can check the value of f.Faddle and see that it has changed.

Now uncomment the line of code in Foo().  Repeat the exercise, but this time note that the Button.Text does not change on 'f' until the SET statement executes for 'Button'.  Since we change the variable pointer for the button parameter inside of Foo, the changes we make to that button are not part of 'f' until the SET statement updates the property's internal variable.

It is foggy but I kind of get why it is happening.  Are you saying I should change the implementation of the property set in the DTO or the BO?

Sean

JonnyBee replied on Thursday, April 10, 2014

From what I can figure out you have 2 options:

  1. Change the underlying DTO object to expose fields and not properties
  2. Change the behavior in the BO to support wrapped DTO with properties

In case of 2)

I always recommend that you have your own intermediate base class so that you can alter/add behavior in the base classes.

Ex inheritance hierarchy: 

Csla.BusinessBase<T>

   - YourApp.MyBusinessBase<T>

            -  YourBusinessObject : YourApp.MyBusinessBase<YourBusinessObject>

This structure allows you to add new bhavior or overrides in YourApp.MyBusinessBase class.

What you need to get - is the System.Reflection.MethodInfo of the underlying property as this will allow you to call the property get/set methods on the given object. I would first try to use another name - ex SetProperty2 - and work out how the code and parameters must be.

swegele replied on Thursday, April 10, 2014

OK I already have an intermediate base class MyBusinessBase, so I can try both your suggestions.  I will try both.  #1 would be editing the code generator for the DTOs.

Re #2, I feel like an idiot but I can't picture what you are saying in my mind.  Do you have a simple example to help me see what you are saying.  Just a simple string property example of some kind?

Thanks a million!

JonnyBee replied on Thursday, April 10, 2014

Explanation of properties: https://msmvps.com/blogs/luisabreu/archive/2007/09/17/properties-on-c.aspx 

For the other part google PropertyInfo or look at Csla.Reflection.MethodCaller or search and look at FasterFlect.

swegele replied on Thursday, April 10, 2014

The part I am not getting is how would the set line change?  I just need a hint to get me going.  

        Set(ByVal value As String)

            SetProperty(EvidNumPropertyInfo, mDALObject.EvidNum, value)

 

        End Set


JonnyBee replied on Thursday, April 10, 2014

Semantically - in C#:

public class MyBO
{
   MyDTO _myDto = new MyDTO();
 
  private PropertyInfo StringVal1Property;
  public string StringVal1
  {
    get { return _myDto.S1; }
    set
    {
      SetProperty2(StringVal1Property, value, _myDto, "S1");
    }
  }
 
  void SetProperty2<T>(PropertyInfo property, T newValue, object obj, string propertyName) where T:IComparable
  {
    var method = obj.GetType().GetProperties().First(p => p.Name == propertyName);
    T orgValue = (T)method.GetValue(obj);
    if (orgValue.CompareTo(newValue) != 0)
    {
      method.SetValue(obj, newValue);
      PropertyHasChanged(property);
    }
  }
 
  protected void PropertyHasChanged(PropertyInfo property)
  {
 
  }
}

swegele replied on Thursday, April 10, 2014

AHA!  OK I see what you are saying now.  And I see where you would put that in the intermediate base class.

Am I correct that I would also have to add the bits that do this in the framework since I am bypassing SetProperty:

        if (_bypassPropertyChecks || CanWriteProperty(propertyInfo, noAccess == Security.NoAccessBehavior.ThrowException))

?

JonnyBee replied on Thursday, April 10, 2014

Yes. 

You know about the Csla.Data.DataMapper class - right?

This class can be used to map (copy) values between 2 objects and creates a default map provided the properties have the same name in both objects!

I believe what you are experiencing is specific to VB.NET and "Copy Back ByRef" functionality
See: http://blogs.msdn.com/b/vbteam/archive/2010/01/26/the-many-cases-of-byref.aspx  

swegele replied on Thursday, April 10, 2014

You are right...C# would have thrown a compiler error trying to pass in a property ByRef.  VB.NET allows it and I see why that is kind of a problem.  How would it know the property is not ReadOnly etc.  

So, thanks a gazillion to you, I know I can do one of the following:

Re DataMapper...it is a little too all or nothing for me.  I do use it here and there...but there are downsides I struggle with:

My way, if the DTO gets updated in the Property Setter then everything is taken care of and values will get persisted.  And as a bonus all the relevant logic for setting code is in the setter...rather than both in the Setter and the TransferTo/FromDB methods.

Thanks again Jonny!

swegele replied on Thursday, April 10, 2014

What if I change it to a tandem approach...I could change from this:

    Private Shared EvidNumPropertyInfo As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.EvidNum, RelationshipTypes.PrivateField)

    Public Property EvidNum() As String

        Get

            Return GetProperty(EvidNumPropertyInfo, mDTO.EvidNum)

        End Get

        Set(ByVal value As String)

            SetProperty(EvidNumPropertyInfo, mDTO.EvidNum, value)

        End Set

 

    End Property

To This:

    Private Shared EvidNumPropertyInfo As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.EvidNum)

    Public Property EvidNum() As String

        Get

            Return GetProperty(EvidNumPropertyInfo, mDTO.EvidNum)

        End Get

        Set(ByVal value As String)

            SetProperty(EvidNumPropertyInfo, value)

            If mDTO.EvidNum <> GetProperty(EvidNumPropertyInfo) Then mDTO.EvidNum = value

        End Set

 

    End Property

My overall goal is to keep the DTO in sync as the BO changes.  This enables me to:
  1. avoid a separate LoadBOFromDTO and LoadDTOFromBO in the in/out from SQL data access
  2. have ability to transfer guts from a ReadOnly BO to an Editable BO internally by passing a DTO

swegele replied on Thursday, April 10, 2014

OK this is starting to make sense.  I think I get the problem.

Others have explained that you can't do this in C#. In VB.NET, you can do this, even with option strict/explicit on:

Option Strict On
Option Explicit On
Imports System.Text

Module Test

   Sub Main()
       Dim sb as new StringBuilder
       Foo (sb.Length)
   End Sub

   Sub Foo(ByRef x as Integer)
   End Sub

End Module

The above code is equivalent to this C# code:

using System.Text;

class Test
{
     static void Main()
     {
         StringBuilder sb = new StringBuilder();
         int tmp = sb.Length;
         Foo(ref tmp);
         sb.Length = tmp;
     }

     static void Foo(ref int x)
     {
     }
}

Personally I'm glad that C# doesn't have this - it's muddying the waters quite a lot, particularly in terms of the value of the property if the parameter is set within the method but then an exception is thrown.

But Jonny, I still do not understand your suggesting for using reflection in an intermediate base class.

 

Copyright (c) Marimer LLC