Handling Read-Only Info Properties in Business Objects

Handling Read-Only Info Properties in Business Objects

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


dpk posted on Wednesday, December 01, 2010

This is part question and hopefully part discussion. I've been developing business objects using CSLA for many years and frequently come across the need to include what I would describe as read-only info properties on a business object. Let me give a quick example. Say you have a Customer class and a CustomerType class. The CustomerType defines different, well, customer types such as "retail", "wholesale", etc. There is also a CustomerTypeInfo class for read-only lookup and a CustomerTypeList class used to load up a collection of CustomerTypeInfo objects from the data store. CustomerTypeInfo defines a unique id (Guid) and a (string) description. Now back to the Customer class. Customer defines a property called CustomerTypeId (Guid). I want to be able to set this property to a value from a CustomerTypeInfo object. (There's also a business rule in Customer that requires this field to be set to a valid Guid).

Now, I also want to have a read-only info property (CustomerTypeDescription) that provides the description for the CustomerType referenced by Customer.CustomerTypeId. This needs to get set in a couple of ways:

1. When the Customer object is loaded from the DB.
2. When the CustomerTypeId property gets set.

In the first case I simply load the CustomerType object from the DB and set the property. In the second case I need to handle both when the CustomerTypeId value provides is valid and not valid (or empty).

Here's the question: What strategies would you use to maintain the CustomerTypeDescription property?

I've used a few:

1. CustomerTypeId's setter is private and I provide a method called SetCustomerType that takes an input parameter of type CustomerTypeInfo (and/or CustomerType). This extracts the necessary values and populates the read-only properties in Customer.

2. Similar to #1, but instead expose a CustomerType property in Customer that exposes the CustomerTypeInfo object passed into SetCustomerType.

3. Allow the CustomerTypeId's setter to load the CustomerType object from the DB and store the local values.

I have run into various problems with each approach.

#1 : Not good for databinding.

#2 : Breaks encapsulation a bit.

#3 : Makes basic unit testing more challenging because the unit test becomes an integration test.

None of these approaches really feels right to me. I would appreciate hearing how others approach this. My example is very simple but I'm running into more complex examples in practice. For example, imagine an order entry screen where the user is adding products by entering in a product code which then populates a description, pulls package size, pricing and other product-related data. When the ProductCode property of the OrderDetail object gets set, do you load the Product object? Use a cache?

Thanks for any advice and feedback.

Dave

JonnyBee replied on Wednesday, December 01, 2010

Hi,

BusinessRules is much more than just Validation Rules, which is why the name was changed in Csla4 to BusinessRules.

So my recommendation (in csla4) is to do this by a business rule that runs when CustomerType has changed.  BusinessRules are much easier to test now that we can create our own RuleContext and test input/output values. Meaning BusinessRules may have OutputValues that will set properties in the BusinessObject (using LoadProperty) .

I'd assume that CustomerTypes is probably a name value list here.

Serverside (Data Access Layer) can still do a BypassPropertyCheck or use LoadProperty that will not trigger ValidationRules and if you call BusinessRules.CheckRules you will still clal the rule. If this is not desireable then you may even test for ApplicationContext.LogicalExecutionLocation to make sure the rule will only run on the logical client side and not in DataAccess.

JonnyBee replied on Thursday, December 02, 2010

Code sample:

  public class SetStateName : BusinessRule
  {
    public IPropertyInfo StateName { getset; }

    public SetStateName(IPropertyInfo stateIdProperty, IPropertyInfo stateNameProperty)
      : base(stateIdProperty)
    {
      StateName = stateNameProperty;
      InputProperties = new List<IPropertyInfo> { stateIdProperty };
      AffectedProperties.Add(stateNameProperty);
    }

    protected override void Execute(RuleContext context)
    {
      var stateId = (string)context.InputPropertyValues[PrimaryProperty];
      var state = StatesNVL.GetNameValueList().Where(p => p.Key == stateId).FirstOrDefault();
      context.AddOutValue(StateName, state == null ? "Unknown state" : state.Value);
    }
  }

In your BO:
    protected override void AddBusinessRules()
    {
      // call base class implementation to add data annotation rules to BusinessRules 
      base.AddBusinessRules();
      BusinessRules.AddRule(new SetStateName(StateProperty, StateNameProperty));
    }


1 I prefer to keep all my property declaration as standard/simple as possible. So properties should
be just get/set, with a little exception for properties that have private backing fields or uses
lazy loading.

2. Notice how the rule does not know the object type or directly gets/sets values in the object.
The Execute method only need to know the StateName (IPropertyInfo as Outputvalue) and relies on the
RuleEngine to supply the state id as input value (Primaryproperty). This makes it easy to create unit
tests for both the constructor and the execute method as we can create our own testing RuleContext,
supply an input value and check output value.

3. This could just as easy be extended to run async on the client side and syn on server side (omittet to
have sample code as simple as possible) or to extend with more properties to set f.ex product data into
an orderline item. I have also attached the same rule running async on client and sync on server (for
Winforms, WPF and SL), just rename from txt to cs. Or you could just do nothing on the server side and
do the work only when you are on the client side.

4. This will work just fine with databinding.

dpk replied on Wednesday, December 15, 2010

Thanks for the post Jonny. I've had to think about this a bit and I'm not sure I'm on board with the solution for a couple of reasons:

1. Using a business rule to perform some basic data mapping seems like an inappropriate use of "rules". I'm not sure if Rocky intended out values to be used in this way. It's clever, but it feels a bit like a hack to me.

2. If it's the first time I'm looking at this BO it would not be apparent how the StateName property is getting set.

3. In your example you're loading a NVL to get the value. Seems like overkill. Okay, you could cache the list but what about bringing the value of the StateName back when you load the object and not through a property setter? Wouldn't that be more efficient in this case?

A larger question is, what if this is more complex, like setting the ProductCode property on an OrderDetail object. We would need to populate other properties such as ProductDescription, PackageSize, Discount, UnitPrice, etc.

Anyone else want to chime in?

Dave

JonnyBee replied on Wednesday, December 15, 2010

Hi Dave,

1. Would you agree for a start that it is a business rule and maybe a rule that should only do its task when called on the logical client side? I don't mean to imply a lot of data mapping in rules - but as a business rule I'd like to do this task whenever the property gets a new value - so my personal preference is to keep BO properties to be just simple properties and use snippets or codegen.

BusinessRules provide an "easy" way of updating other properties in the BO - and automatically call additional rules for affected properties and notify the UI of changed values.

2. Well, yes at first glance - but when you look at the rules it should be very easy give the rule/class a good descriptive name and even add a comment to the property that a certain rule is executed when this property is changed to set the dependent property XYZ.

3. Loading a NVL is just for the sample - it could just as easily be a command object or a readonly object that would get the values for me.
    In the DAL you should still probably use LoadProperty  or BypassPropertyChecks and set StateName right away.

I'd prefer to implement this as a BusinessRule even if it is several properties like ProductCode, PackageSize, Discount etc.  You could implement it in the property setter for ProductCode but would you also add recalculation of LinePrice and OrderTotal in the setter? To me, all this is BusinessRules - both to lookup additional values and calculate totals.

BusinessRules also supports async rules and call additional rules for affected properties and notify UI of changes -  so to me all the better reason to use an async rule when executed on the client and keep the UI as responsive as possible. The actual property will remain busy while the rules executes but UI will be responsive.

And if you implement this as additional code in your property setter straight out - then in the DAL you can only use LoadProperty on managed properties to set the value. If you use LoadProperty on a PrivateField (registered property)  or BypassPropertyChecks to set the property in your DAL then the addional code will be called. I can't remember if the flag for BypassPropertyCheck is available (probably is) but then you must also be aware and look for that before you execute the additional code.

So - for properties - I am all out for KISS and just keep them as simple as possible.

I'd like to get some more responses here too. Hope other will chime in.

 

dpk replied on Wednesday, December 15, 2010

Jonny,

First, your thoughts are excellent and I appreciate you sharing them with me. You have certainly inspired my thinking on this. I'll respond now to your last post:

1. With all due respect I would not call these business rules. In my mind these are behaviors that the BO must perform in response to an action (in this case a property being set) - at a minimum. However, this is just data that we want to have available for the UI. So it's not really a "rule" per se, rather I see it as state (the behavior part is really about how the data is retrieved / calculated etc.). I do agree with you that we want the setters and getters to be very simple. But calling a method to load this data in the setter is not much more than would be necessary to do lazy-loading for example. Yes, codegen becomes challenging but then again I never expect codegen to do it all.

2. I'll agree with you here, but there's a bit of "magic" going on that the developer needs to understand. Once this idea is known then, yes, it's simple to understand.

3. Yes, yes. I know it's just an example. There are many way to achieve the same result. My main question is, however, how do other deal with more complex scenarios like the OrderDetail example I give.

I truly appreciate your input.

Takk!

Dave

Peran replied on Thursday, December 16, 2010

In the past I have overriden OnPropertyChanged or PropertyHasChanged allowing me to keep my property getters and setters clean.

 

        protected override void PropertyHasChanged(Csla.Core.IPropertyInfo property)
        {
            base.PropertyHasChanged(property);

            if (property == ProductCodeProperty)
            {
                this.SetProductInfo();  // sets ProductDescription, PackageSize, Discount, UnitPrice
            }
        }

 

But I now like the look of Jonnys solution for silverlight async calls as the business rules subsystem will maintain all the meta states IsBusy, CanSave etc whilst the info properties were being set.

 

Peran

Kevin Fairclough replied on Thursday, December 16, 2010

I'm resolving the foreign keys using the Info object, the Info object would have the properties required to display the relationship in the UI.  I may have many Info objects depending on use case.

public static readonly PropertyInfo<ImageInfo> PrimaryImageProperty = RegisterProperty<ImageInfo>(c => c.PrimaryImage);
        public ImageInfo PrimaryImage
        {
            get { return GetProperty(PrimaryImageProperty); }
            set { SetProperty(PrimaryImageProperty, value); }
        }

There is no PrimaryImageId property in my BO.

Kevin

 

ajj3085 replied on Thursday, December 16, 2010

I don't think #2 breaks encapsulation; it's actually composition.  It sounds like your customer class doesn't really care what the value is, so why should it care about the description?

In your order entry screen, there are many ways to handle this.   One would be to loada  ProductInfo with the data you need in the background.  The OrderDetail should delegate to something else; ProductInfo can have a LoadByCode factory method, or perhaps you have a ProductList, which can return a list of products which start with your code, and the user selects one.  But OrderDetails doesn't care.  Whatever actually does the fetching can worry about caching, again it's not the OrderDetail's responsibility.

Also consider loading an OrderDetail from an existing order onto the screen.  Does it load a ProductInfo again? You can, but it wouldn't be great for efficency.  The OrderDetail can do a join on the product table and just pull out the data it needs.  Does it hold a reference to a ProductInfo?  It depends your requirements.  Do you need to keep the order as it was when it was placed?  Or is it ok if the Description changes to match a change to the products description?  It depends on your requirements.

Copyright (c) Marimer LLC