CSLA 4 Cascading Rules

CSLA 4 Cascading Rules

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


pneurohr posted on Thursday, July 22, 2010

I am getting what I think is an interesting behavior and wanted to get some clarification.  I have two properties A and B and two rules X and Z.  Both rules are async.  Rules X sets property A , rule Z sets property B and rule Z is attached to property A.  When I look at the value of property A it appears to be an event behind.  So if A goes from 5 to 10 when I know it is 10 the value is 5 when using the context.InputProperties.  If I look at context.Target it is 10. 

I hope this makes sense and what is the best way to get the value I want (10) in rule Z?

RockfordLhotka replied on Thursday, July 22, 2010

context.InputProperties is a snapshot of the values at the time the rule was invoked.

There is no threadsafe way to get at the object's property values while the async rule is running on the background thread. This is why (by default) Target isn't available to an async rule - if you use Target in an async rule you better know exactly what you are doing or you'll find yourself in deep trouble.

To avoid that trouble, InputProperties gets a copy of the specified property values when the rule is invoked, so the rule has something to work with that is safe (if not necessarily current).

This is all by design.

If you really need access to current object values while the async rule is running, you will need to use Target - and you will need to invent and implement your own threadsafe way to access the Target object properties.

pneurohr replied on Thursday, July 22, 2010

I understand that the InputProperties is a snapshot at the time the rule was invoked.  I guess my question is why would the rule be invoked before the property was updated and the snapshot taken?  I though the out values were updated when context.Complete is called which should be on the UI thread.

RockfordLhotka replied on Thursday, July 22, 2010

You have a dependent property scenario too I assume?

When the rule completes, the out values are used to update the object's properties, true. But that's done using LoadProperty(), which doesn't trigger any rules to run (because that would rapidly lead to infinitely running async rules - just think of the chaos!).

In other words, your rules are both being started (I suspect) at about the same time. The completion of either rule wouldn't trigger the running of more rules, because the out values are "passively" updating the property values.

pneurohr replied on Thursday, July 22, 2010

That makes sense but then why execute the rules on the affected properties at all if their values may very well change during the "first" rule to run thereby making the results of the affected properties rules incorrect?  My understanding was to assure that they were still valid after any changes made in the rule.

RockfordLhotka replied on Thursday, July 22, 2010

The overall assumption here is that async rules are the exception, not the norm. The only real reason to have an async rule is if the rule needs to talk to the server, or perhaps if it executes some long-running task (but I'm not sure that's a great scenario for rules).

I knew from the start that overlapping async rules would be problematic. But short of creating an entirely threadsafe model - which would mean starting from scratch with a completely different architectural model for CSLA - there's no realistic way to make overlapping async rules really work either.

I guess what I'm saying is, that for better or worse, you have the dubious honor of being the first person to hit a scenario that I knew would be hit Smile

pneurohr replied on Monday, July 26, 2010

I appreciate the response but I have another question now.  In the process of converting my rules back to run synchronously it appears as though my UI is no longer being updated.  Instead of calling context.AddOutValue I have to assign it by casting the target and setting the property or raising the OnPropertyChanged event myself.  The bindings have IdentityConverters on them.  Any idea why this might be?

RockfordLhotka replied on Monday, July 26, 2010

The UI updates because of PropertyChanged, so something needs to ensure that PropertyChanged is raised when a property is changed.

You should be able to continue to use AddOutValue() if you'd like - that works in both sync and async rules (the whole idea was that the basic structure of a rule is unaffected by being sync or async - at least at that level of coding).

But in a sync rule that isn't in a rule library (isn't reusable across object types), it is perfectly fine to cast Target to your object type and set properties - as long as you avoid infinite loop issues (like don't set the value of the primary property unless you have a way to short-circuit the resulting re-run of the rules).

pneurohr replied on Monday, July 26, 2010

But why does AddOutValue not seem to call property changed?  If I cast the target then I presume I am calling SetProperty instead of LoadProperty and could potentially run into some authorization issues.

RockfordLhotka replied on Monday, July 26, 2010

AddOutValue ultimately does use LoadProperty(), that's true.

But to add an out value for a property you have to also make that property an AffectedProperty of the rule, and so a PropertyChanged event will be raised for that property.

For example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var obj = new Test();
      obj.PropertyChanged += (o, e) =>
        {
          Console.WriteLine("PropertyChanged {0}", e.PropertyName);
        };
      obj.Value1 = "hi";
      Console.WriteLine(obj.Value2);
      Console.ReadLine();
    }
  }

  [Serializable]
  public class Test : BusinessBase<Test>
  {
    public static readonly PropertyInfo<string> Value1Property = RegisterProperty<string>(c => c.Value1);
    public string Value1
    {
      get { return GetProperty(Value1Property); }
      set { SetProperty(Value1Property, value); }
    }
    public static readonly PropertyInfo<string> Value2Property = RegisterProperty<string>(c => c.Value2);
    public string Value2
    {
      get { return GetProperty(Value2Property); }
      set { SetProperty(Value2Property, value); }
    }

    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new MyRule { PrimaryProperty = Value1Property });
    }

    private class MyRule : Csla.Rules.BusinessRule
    {
      public MyRule()
      {
        AffectedProperties.Add(Value2Property);
      }

      protected override void Execute(Csla.Rules.RuleContext context)
      {
        context.AddOutValue(Value2Property, "abc");
      }
    }
  }
}

Copyright (c) Marimer LLC