Moving from 3.8 to 4.5 confused about best way to go with private backing fields

Moving from 3.8 to 4.5 confused about best way to go with private backing fields

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


swegele posted on Sunday, February 16, 2014

I just got a contract to upgrade a pretty unique CSLA 3.8 business layer to 4.5. Because of the way they do property backing I am confused as to how to move forward.  Oh and it is VB.NET :-(

Several things that stand out:

  1. Root editable object receives and wraps a DTO which represents all the data (full object graph of root and all children).  The DTO has the root and up to grandchildren within it.  So the root editable object hydrates its own properties with the DTO and then hands the parts of the DTO to get child business objects created.  This happens when the data portal is crossed.  So the actual.
  2. The DTO within the business objects has its own change tracking...and the Data Access Layer is already coded to deal with the DTOs.
  3. They manually handled isChild, isDirty, isValid, and PropertyHasChanged

So here is a quick example...i'll try to keep it brief and to the point

Public Class RootPerson (inherits BusinessBase)

  private mPersonDTO

  <nonserialized><notundoable> private mPhoneNumbers as PhoneNumberList

  Public Property LastName As String

    Get

       Return mPersonDTO.LastName

    End Get

        Set(ByVal value As String)
            If mPersonDTO.LastName <> value Then
                mPersonDTO.LastName = value
                PropertyHasChanged("LastName")
            End If
        End Set

  End Property

  Public Property PhoneNumbers As PhoneNumbersList

    Get

      Return mPhoneNumbers

    End Get

  End Property

  <after returning from the Data Portal a method HydrateChildLists() is called (not included here for brevity)

  Private Sub HydrateChildLists()

    mPhoneNumberList = PhoneNumberList.NewFromDTO(mPersonDTO.PhoneNumbers)

  End Sub

  Public Overrides ReadOnly Property IsValid() As Boolean

        Get
            Dim blnValid As Boolean

            blnValid = MyBase.IsValid _
                AndAlso mPhoneNumberList.IsValid

            Return blnValid
        End Get
    End Property

    Protected Overrides Sub PropertyHasChanged(ByVal propertyName As String)
        'overridden so we don't call mark dirty since the DTO takes care of that
        ValidationRules.CheckRules(propertyName)
        OnPropertyChanged(propertyName)
    End Sub

End Class   

___________________________________

I think you get the idea.  It was the old way of manually doing the business objects with manual tracking and does not use propertyinfo helpers. 

I guess given the thousands of lines of code that is already working...I am wondering if I should just stick with it or rip the guts out and redo using propertyinfo fields?

The only problem I see with sticking with the way it is...is there is no longer an overrides for PropertyHasChanged(propertyName As String).  I think that is the only thing I would need to deal with.

I could upgrade the bazillions on properties yes...but whats the payoff if the stuff is already written?  I am a practical guy.

but on that note...if I were to upgrade all the properties...would this be the best way?

    Public Shared LastNameProperty As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.LastName, RelationshipTypes.PrivateField)
    Public Property LastName() As String
        Get
            Return GetProperty(LastNameProperty, mPersonDTO.LastName)
        End Get
        Set(ByVal value As String)
            SetProperty(LastNameProperty, mPersonDTO.LastName, value)
        End Set
    End Property

Thanks guys & gals

 

swegele replied on Sunday, February 16, 2014

Ugh, now I see you can no longer call BusinessRules.CheckRules(propertyName As String)...so if I get this right the business rules now require propertyInfo as the backbone.

double ddddrats foiled again

 

swegele replied on Sunday, February 16, 2014

So now I am asking myself...I have already upgraded all the other frameworks and tooling except CSLA. 

It is all working...until I point the project reference to CSLA 4.5.  So why upgrade from 3.8 and take the monstrous hit of upgrading ALL the properties/business rules/etc.  I mean is 4.5 way faster or does it have such sweet low hanging fruit that I can't resist?

Any thoughts greatly appreciated!

swegele replied on Sunday, February 16, 2014

I'll just keep talking to myself :-)

I really really like some of the 4.5 cleanup using Generics:

I just have to suck it up and do it.  One stinking property at a time.  Ugh.  I will feel better when it is done.

While I am in there I can get rid of all the manual status tracking and future business layer developers will be happily ignorant at how simple it is to make a business object now.

JonnyBee replied on Monday, February 17, 2014

Hi,

Some things to remember: 

You may however be able to use most of the old rules (3.8) provided that you follow these guidances: https://jonnybekkum.wordpress.com/2013/10/07/csla-validationthe-new-nuget-package-in-csla-net-4-5-40/  

If you use R# then look into the Structural Search And Replace function. I have found this to be very useful in upgrades (however I have only used it in C# projects -  not sure if this is supported for VB projects).

 

swegele replied on Monday, February 17, 2014

Hi Jonny et al.,

Well about 23 hours later I am done updating all the private backing properties in every editable object to use the propertyinfo(of T).  Can I just say...O M G!!  What a pain.  I'm still miserable so I am sure I will feel happy about it later.  wow.

Anyway would you mind saying more about your last point...about override/overload of non-generic LoadProperty thingy?  Any examples?

 

JonnyBee replied on Wednesday, February 19, 2014

The challenge is that the rule engine has absolutely no knowledge of your private fields. So in order to supply the value or update the field the rule engine will use the non-generic overloads.

ex:

private int _num1;
public static readonly PropertyInfo<int> Num1Property = RegisterProperty<int>(c => c.Num1, RelationshipTypes.PrivateField);
public int Num1
{
  get { return GetProperty(Num1Property, _num1); }
  set { SetProperty(Num1Property, ref _num1, value); }
}

protected override void LoadProperty(IPropertyInfo propertyInfo, object newValue) {   if (propertyInfo == Num1Property)     _num1 = (int)newValue;   else      base.LoadProperty(propertyInfo, newValue); } protected override object ReadProperty(IPropertyInfo propertyInfo) {   if (propertyInfo == Num1Property)     return _num1;   else      return base.ReadProperty(propertyInfo); }

and you use a standard rule like:

BusinessRules.AddRule(new MinValue<int>(Num1Property, 1));
 

then the rule engine will need to call the non-generic ReadProperty to get the actual field value.

swegele replied on Wednesday, February 19, 2014

That is scary...I am seeing a huge "If" statement for every propertyinfo with a private backing field in my class??? No way.  I must not be understanding something.

What are the implications if I don't have that override for LoadProperty and ReadProperty?

Man thanks for the help Jonny

swegele replied on Wednesday, February 19, 2014

If I am using private backing fields why would I use LoadProperty and ReadProperty anyway...if I want to skip the business rules I just set the value into the private backing field directly right?

Instead of LoadProperty(namePropertyInfo, "Doe")

Wouldn't I just do privateNameBackingField = "Doe"

Then in the property Set I would call the normal SetProperty(namePropertyInfo, privateNameBackingField, value)

Thanks,

JonnyBee replied on Thursday, February 20, 2014

The new rule engine has new functionality. You can declare: 

AND - the key point is that the rules does not need to have any knowledge of the actual business object - and hence it is much easier to create unit tests for these new rules and also easier to create asyncronous rules. You can create a "fake" RuleContext and send in as parameter to the execute method. 

Take f.ex this ToUpper rule: 

public class ToUpper : Csla.Rules.BusinessRule
{
  public ToUpper(IPropertyInfo primaryProperty) : base(primaryProperty)
  {
    InputProperties = new List<IPropertyInfo>(){primaryProperty};
    AffectedProperties.Add(primaryProperty);
  }
 
  protected override void Execute(RuleContext context)
  {
    var value = context.GetInputValue<string>(PrimaryProperty);
    context.AddOutValue(PrimaryProperty, value.ToUpper());
  }
}

This rule has NO knowledge of the business object (it does get the business object
as one of the properties on RuleContext). In order to work properly with private backing fields the rule engine
uses the non.generic ReadProperty / LoadProperty og get or set the private field value.

My key issue is that there is a LOT you must make sure to NOT use, such as the standard rules, the CslaContrib rules
or the CslaGenFork rules.

I am NOT saying that you should change the property declaration that you have. The are correct now as is.
BUT you need the non generic LoadProperty / ReadProperty overloads in order to use the new rules engine safely.

Another example would be the builtin Required rule:

public class Required : CommonBusinessRule
{
  public Required(Csla.Core.IPropertyInfo primaryProperty) : base(primaryProperty)
  {
    InputProperties.Add(primaryProperty);
  }
 
  public Required(Csla.Core.IPropertyInfo primaryProperty, string message) : this(primaryProperty)
  {
    MessageText = message;
  }

  public Required(Csla.Core.IPropertyInfo primaryProperty, Func<string> messageDelegate ) : this(primaryProperty)   {     MessageDelegate = messageDelegate;   }   protected override string GetMessage()   {     return HasMessageDelegate ? base.MessageText : Csla.Properties.Resources.StringRequiredRule;   }   protected override void Execute(RuleContext context)   {     var value = context.InputPropertyValues[PrimaryProperty];     if (value == null || string.IsNullOrWhiteSpace(value.ToString()))     {       var message = string.Format(GetMessage(), PrimaryProperty.FriendlyName);       context.Results.Add(new RuleResult(RuleName, PrimaryProperty, message) { Severity = Severity });     }   } }

Just as the ToUpper - this rule does not act directly with the business object. So for private backing fields
the Rule engine expects the business object to override ReadProperty in order to supply the actual value.

swegele replied on Thursday, February 20, 2014

OK I get the "why" now...very helpful level of detail.  You are saying the generic rules use ReadProperty and LoadProperty even if I choose not to in my code.   So if I am understanding you correctly...

If I have a business object with 35 properties with private backing fields...in my ReadProperty overload I would have 1 If and 34 ElseIf lines? 

I 'could' do that but ugh.  I remember Rocky saying at a conference, you probably have something designed improperly if you find a Select Statement or an If Statement like that in your code.

Problem comes when another developer adds a property to the interface...adds the property code but forgets or didn't know to add a line in the Read & Load overloads...then no one will know he made an error until runtime and gets very wierd symptoms. 

Yuck.

It seems like the takeaway is "Use CSLA's managed properties and all will be happy and sunshine...you can use your own private backing fields but you will suffer eventually"

 

JonnyBee replied on Friday, February 21, 2014

Yes, 

The recommended solution is to use managed properties and only use private backing fields for Intergraph-references. (this is my preferences). Intergraph references must be marked with NonSerialized and NotUndoable attributes and this is only possible on private backing fields. 

When you use private backing fields the values is not automatically serialized/deserialized or included in N-Level undo when you use the MobileFormatter as serializer . Which serializer to use is configurable by CslaSerializationFormatter setting and the default value is BinaryFormatter. 

swegele replied on Saturday, February 22, 2014

Along with having to bake in my own handling of N-level-undo, I just found another reason to stick with recommended (managed properties without private backing fields)

Didn't notice this in Rocky's CSLA 4 book so I assume it came after that.

FieldManager.UpdateChildren(this)

That is a nice feature to automatically call all child dataportals and isNew,isDeleted,isDirty is handled.  That makes it nice and easy to handle lazy load scenarios...whatever is there gets saved.

Again, thanks for all the help Jonny

swegele replied on Sunday, February 23, 2014

Hey Johnny or whoever...do you put a start / end tag of some kind to get code in your post to look like source code with the color formatting etc.? <code> </code> or something like that

JonnyBee replied on Monday, February 24, 2014

I use Firefox or IE (as Google Chrome removes the html tags on paste) and have Visual Studio 201x Productivity Power Tools installed.

The PPT have an option for HTML copy that allows me to copy the code with color codes and formatting.

swegele replied on Tuesday, February 25, 2014

Jonny et al,

Can you point me to the place in the CommonRules source that uses LoadProperty and/or ReadProperty?  I am not seeing it.

Thanks,

 

swegele replied on Tuesday, February 25, 2014

OK I found it at this post....

http://forums.lhotka.net/forums/p/10037/47103.aspx#47103

It comes in if I use OutValues

JonnyBee replied on Tuesday, February 25, 2014

Hi,

Please post new questions as new threads rather than continuing on a post that is already marked as "answered". 

Copyright (c) Marimer LLC