Multiple Properties, Business Rules, & and Objects changing their valid state

Multiple Properties, Business Rules, & and Objects changing their valid state

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


Chris62 posted on Monday, January 09, 2012

I have a biz object that has a business rule that requires one of the two properties to be set, but not both has to be set.  The rule works fine when one of the options is set and then saved.  The problems arise when a user might want to reassign work from a user to a group(or vice versa).  In code I set the old to null, and sets the other to the appropriate id.  Below is an example:

[Serializable]
public partial class Work : BusinessBase<Work>
{
// Primary key in DB.
private static readonly PropertyInfo<System.Int32> IdProperty = RegisterProperty<System.Int32>(p => p.Id, "Work Id");
public System.Int32 Id
{
get { return GetProperty(IdProperty); }
set { SetProperty(IdProperty, value); }
}

private static readonly PropertyInfo<System.Int32?> UserIdAssignToProperty = RegisterProperty<System.Int32?>(p => p.UserIdAssignTo, "User Id Assign To");
public System.Int32? UserIdAssignTo
{
get { return GetProperty(UserIdAssignToProperty); }
set { SetProperty(UserIdAssignToProperty, value); }
}

private static readonly PropertyInfo<Group> GroupIdAssignToProperty = RegisterProperty<Group>(p => p.GroupIdAssignTo, "Group Id Assign To");
public System.Int32? GroupIdAssignTo
{
get { return GetProperty(GroupIdAssignToProperty); }
set { SetProperty(GroupIdAssignToProperty, value); }
}

protected override void AddBusinessRules()
{

base.AddBusinessRules();
BusinessRules.AddRule(new AssignWork(UserIdAssignToProperty, GroupIdAssignToProperty));
BusinessRules.AddRule(new AssignWork(GroupIdAssignToProperty, UserIdAssignToProperty));
}
// root factory methods
// root data portal
}
 

internal class AssignWork : BusinessRule
{
public AssignWork(IPropertyInfo PrimaryProperty, IPropertyInfo SecondaryProperty)
        : base(PrimaryProperty)
{
if (InputProperties == null)
{
InputProperties = new List<IPropertyInfo>();
}
InputProperties.Add(PrimaryProperty);
InputProperties.Add(SecondaryProperty);
}

protected override void Execute(Csla.Rules.RuleContext context)
{
base.Execute(context);
IPropertyInfo groupProp = InputProperties.Find(prop => prop.FriendlyName.Equals("Group Assign To"));
IPropertyInfo userProp = InputProperties.Find(prop => prop.FriendlyName.Equals("User Id Assign To"));

var userId = (dynamic)context.InputPropertyValues[userProp];
var groupId = (dynamic)context.InputPropertyValues[groupProp];

if (userId == null && groupId == null))
{
context.AddErrorResult(String.Format("Either assign to {0} or {1}", userProp.FriendlyName, groupProp.FriendlyName));
}
else if (userId != null && groupId != null))
{
context.AddErrorResult(String.Format("Can not assign both {0} and {1}", userProp.FriendlyName, groupProp.FriendlyName)); 
}
}


// Unit Test  Case
[Test]
public void Test1()
{
Work myWork = Work.NewWork();
myWork.UserIdAssignTo = 123; 
myWork = myWork.Save(); // everything works ok so far.
int workId = myWork.Id
// let's say a user wants to assign work from user to a group
Work reassignWork = Work.GetWork(workId);  // at this point: UserIdAssignTo = 123
reassignWork.UserIdAssignTo = null; // Object is in an invalid state here
reassignWork.GroupIdAssignTo = 456; // Object *should* no longer be in an invalid state, all rules satisfied
reassignWork = reassignWork.Save(); // getting an error here, says Object is not valid.
}
[Test]
public void Test2()
{
Work myWork = Work.NewWork();
myWork.GroupIdAssignTo = 456; 
myWork = myWork.Save(); // everything works ok so far.
int workId = myWork.Id
// let's say a user wants to assign work from group to a user
Work reassignWork = Work.GetWork(workId);  // at this point: GroupIdAssignTo = 456
reassignWork.GroupIdAssignTo = null; // Object is in an invalid state here
reassignWork.UserIdAssignTo = 123; // Object *should* no longer be in an invalid state, all rules satisfied
reassignWork = reassignWork.Save(); // getting an error here, says Object is not valid.
}

The problem is in a sense the object enters an invalid state, when both GroupId & UserId are both null, even though it is ultimately resolved.  I'm not sure of an elegant way of handling this without overriding the Save() method and doing a BuisnessRule.CheckRules() call.

JonnyBee replied on Monday, January 09, 2012

Which versjon of CSLA?

Your existing code would run as expected with CSLA 4.2 as the Rule engine considers InputProperties as Dependency.

In CSLA 4.1/4.0 only AffectedProperties is used for Dependency so your rule must add secondaryProperty to AffectedProperties.

This code and tests runs OK  for CSLA 4.1:

 [Serializable]
  public partial class Work : BusinessBase<Work>
  {
    private static readonly PropertyInfo<System.Int32> IdProperty = RegisterProperty<System.Int32>(p => p.Id, "Work Id");
    public System.Int32 Id
    {
      get { return GetProperty(IdProperty); }
      set { SetProperty(IdProperty, value); }
    }
 
    private static readonly PropertyInfo<System.Int32?> UserIdAssignToProperty = RegisterProperty<System.Int32?>(p => p.UserIdAssignTo, "User Id Assign To");
    public System.Int32? UserIdAssignTo
    {
      get { return GetProperty(UserIdAssignToProperty); }
      set { SetProperty(UserIdAssignToProperty, value); }
    }
 
    private static readonly PropertyInfo<System.Int32?> GroupIdAssignToProperty = RegisterProperty<System.Int32?>(p => p.GroupIdAssignTo, "Group Id Assign To");
    public System.Int32? GroupIdAssignTo
    {
      get { return GetProperty(GroupIdAssignToProperty); }
      set { SetProperty(GroupIdAssignToProperty, value); }
    }
 
    protected override void AddBusinessRules()
    {
 
      base.AddBusinessRules();
      BusinessRules.AddRule(new AssignWork(UserIdAssignToProperty, GroupIdAssignToProperty));
      BusinessRules.AddRule(new AssignWork(GroupIdAssignToProperty, UserIdAssignToProperty));
    }
  }
 
 
  internal class AssignWork : BusinessRule
  {
    public IPropertyInfo SecondaryProperty { getset; }
 
    public AssignWork(IPropertyInfo primaryProperty, IPropertyInfo secondaryProperty) : base(primaryProperty)
    {
      SecondaryProperty = secondaryProperty;
      if (InputProperties == null)
        InputProperties = new List<IPropertyInfo>();
 
      InputProperties.Add(primaryProperty);
      InputProperties.Add(secondaryProperty);
      AffectedProperties.Add(secondaryProperty);
    }
 
    protected override void Execute(Csla.Rules.RuleContext context)
    {
      var value1 = (dynamic) context.InputPropertyValues[PrimaryProperty];
      var value2 = (dynamic) context.InputPropertyValues[SecondaryProperty];
 
      if (value1 == null && value2 == null)
      {
        context.AddErrorResult(String.Format("Either assign to {0} or {1}", PrimaryProperty.FriendlyName, SecondaryProperty.FriendlyName));
      }
      else if (value1 != null && value2 != null) 
      {
        context.AddErrorResult(String.Format("Can not assign both {0} and {1}", PrimaryProperty.FriendlyName, SecondaryProperty.FriendlyName));
      }
    }
  }
 
  [TestClass]
  public class TestWork
  {
    [TestMethod]
    public void Test1()
    {
      Work myWork = new Work();
      myWork.UserIdAssignTo = 123;
      Assert.IsTrue(myWork.IsValid);
      myWork.UserIdAssignTo = null// Object is in an invalid state here
      Assert.IsFalse(myWork.IsValid);
      myWork.GroupIdAssignTo = 456; // Object *should* no longer be in an invalid state, all rules satisfied
      Assert.IsTrue(myWork.IsValid);
      Assert.IsTrue(myWork.IsSavable);
    }
 
    [TestMethod]
    public void Test2()
    {
      Work myWork = new Work();
      myWork.GroupIdAssignTo = 456;
      Assert.IsTrue(myWork.IsValid);
      myWork.GroupIdAssignTo = null// Object is in an invalid state here
      Assert.IsFalse(myWork.IsValid);
      myWork.UserIdAssignTo = 123; // Object *should* no longer be in an invalid state, all rules satisfied
      Assert.IsTrue(myWork.IsValid);
      Assert.IsTrue(myWork.IsSavable);
    }
  }

 

Chris62 replied on Tuesday, January 10, 2012

Thanks again JohnnyBee, I upgraded to 4.2.2 and it works.

Copyright (c) Marimer LLC