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:
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
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
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!
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.
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).
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?
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.
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
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,
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.
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"
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.
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
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
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.
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,
OK I found it at this post....
http://forums.lhotka.net/forums/p/10037/47103.aspx#47103
It comes in if I use OutValues
Hi,
Please post new questions as new threads rather than continuing on a post that is already marked as "answered".
Copyright (c) Marimer LLC