Call DataPortal From A PropertyRule

Call DataPortal From A PropertyRule

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


Chris62 posted on Tuesday, February 14, 2012

I have a PropertyRule that needs to call into the database for values to verify against and I'm having problems with the DataPortal.  I'm getting an "Object is not valid and can not be saved" error message, but it's erroring out before I get to the save section.  It's erroring on the PropertyRule call

This is the Parent Business Class

[Serializable]
public class BizObject : BusinessBase
{
private readonly static PropertyInfo IdProperty = RegisterProperty(p => p.Id, "Primary Key", 0);
public int Id
{
get { return GetProperty(IdProperty); }
private set { SetProperty(IdProperty, value); }
}
public readonly static PropertyInfo SomeOtherIdProperty = RegisterProperty(p => p.SomeOtherId, "Some Other Id", 0);
public int SomeOtherId
{
get { return GetProperty(IdProperty); }
set { SetProperty(SomeOtherIdProperty, value); }
}
public readonly static PropertyInfo AnotherIdProperty = RegisterProperty(p => p.AnotherId, "Another Id", 0);
public int AnotherId
{
get { return GetProperty(AnotherIdProperty); }
set { SetProperty(AnotherIdProperty, value); }
}
// normal factory methods for Parent object
protected override void AddBusinessRules()
{
base.AddBusinessRules();
BusinessRules.AddRule(new AdditionRule(SomeOtherIdProperty, new List() { AnotherIdProperty}));
}
// normal dataportal_xyz for Parent object
}

This is the Business Object that will retrieve the data from the data base

[Serializable]
public class AnotherBizObject : BusinessBase


{
// usual propeties, factories, data portals, etc...
public static int GetReturnId(int AnotherID)
{
GetReturn cmd = new GetReturn() { AnotherID = AnotherID };
cmd = DataPortal.Execute(cmd);
return cmd.ReturnId;
}

[Serializable]
private class GetReturn : CommandBase
{
public int AnotherID { get; set; }
public int ReturnId { get; set; }

protected override void DataPortal_Execute()
{
// call to database here, use AnotherID to search on, return ReturnId
ReturnId = 10;
}
}
}

Here's the business rule used in the Parent Object

[Serializable]
public class AdditionRule : PropertyRule
{
public AdditionRule(IPropertyInfo Primary, IEnumerable Properties) : base(Primary)
{
AffectedProperties.AddRange(Properties);
}

protected override void Execute(RuleContext context)
{
base.Execute(context);

int someOtherId = (int)ReadProperty(context.Target, BizObject.SomeOtherIdProperty);
int anotherID = (int)ReadProperty(context.Target, BizObject.AnotherIdProperty);
if (someOtherId > 0 && anotherID > 0)
{
int returnID = AnotherBizObject.GetReturnId(anotherID);

if (returnID != (someOtherId + anotherID))
{
context.AddErrorResult("Error");
}
}
}
}

It errors on the line "int returnID = AnotherBizObject.GetReturnId(anotherID);"  It doesn't like going through the dataportal.  Also this works running through Visual Studio(localhost), but it fails when I push up to a dev/test environment(which is making debugging a treat).

Chris62 replied on Tuesday, February 14, 2012

Here's the stack trace:

Object is not valid and can not be saved

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: Csla.Rules.ValidationException: Object is not valid and can not be saved

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[ValidationException: Object is not valid and can not be saved]
   Csla.BusinessBase`1.Save() +303
   Surveys.Controllers.SurveyController.Save(FormCollection controls) in C:\Code\SurveySystem\Main\Surveys\Surveys\Controllers\SurveyController.cs:107
   lambda_method(Closure , ControllerBase , Object[] ) +108
   System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +17
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +208
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
   System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +55
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +263
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +19
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +191
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
   System.Web.Mvc.Controller.ExecuteCore() +116
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
   System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
   System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
   System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
   System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8966925
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184

We're running CSLA 4.2.2.0

JonnyBee replied on Tuesday, February 14, 2012

Hi,

This should work just fine.

So what is the error message that you get from the AnotherBizObject?

You may also consider to inhert from Csla.Rules.PropertyRule and set

CanRunInCheckRules = true
CanRunOnServer = false
CanRunAsAffectedProperty = false

This will deny the rule from running in Data Access (serverside of data portal), allow in CheckRules() on client and deny as affected property from another property that was changed. The effect is that the rule only runs when the user edits the property in UI.

 

 

 

JonnyBee replied on Tuesday, February 14, 2012

Hmm, I'd also rather have the rule call the GetReturn commandobject directly and not got to AntoherBizObject.

So I'd prefer to put this static factory code inside the public available GetReturn command object (following the Singe Responsibility pattern):

public static int GetReturnId(int AnotherID)
{
GetReturn cmd = new GetReturn() { AnotherID = AnotherID };
cmd = DataPortal.Execute(cmd);
return cmd.ReturnId;
}

Or explicitly specify the object type on DataPortal.Execute

public static int GetReturnId(int AnotherID)
{
GetReturn cmd = new GetReturn() { AnotherID = AnotherID };
cmd = DataPortal.Execute<GetReturn>(cmd);
return cmd.ReturnId;
}

From your error message it is clear that Execute (which actually calls Save) is called on a BusinessBase object and NOT the CommandBase object.

Chris62 replied on Tuesday, February 14, 2012

I (hopefully) changed it around to what you're suggesting:

[Serializable]
public class AnotherBizObject : BusinessBase


{
// usual propeties, factories, data portals, etc...
}

[Serializable]
public class GetReturn : CommandBase
{
public static int GetReturnId(int AnotherID)
{
GetReturn cmd = new GetReturn() { AnotherID = AnotherID };
cmd = DataPortal.Execute(cmd);
return cmd.ReturnId;
}
private readonly static PropertyInfo AnotherIDProperty = RegisterProperty(p => p.AnotherID, "Another Id");
public int AnotherID { get; set; }
private readonly static PropertyInfo ReturnIDProperty = RegisterProperty(p => p.ReturnId, "Return Id");
public int ReturnId { get; set; }

protected override void DataPortal_Execute()
{
// call to database here, use AnotherID to search on, return ReturnId
ReturnId = 10;
}
}

In the AdditionRule I changed it to:

// snip
int anotherID = (int)ReadProperty(context.Target, BizObject.AnotherIdProperty);

if (someOtherId > 0 && anotherID > 0)
{
int returnID = GetReturn.GetReturnId(anotherID);  // changed to use new object GetReturn instead

// snip

P.S. this isn't the actual code, I stripped out the other pieces that are working and this is a simplified example

JonnyBee replied on Tuesday, February 14, 2012

Hi,

Ok, one more major problem.

Do not mix propertyinfo and unmanaged properties like this. Use the snippets from the support folder when you declare properties.

DataPortal will create a "clone" of the object before calling Execute (actually Save) and your fields will have a value of 0 (the defalt value).

Your properties must be declared like this:

private readonly static PropertyInfo ReturnIDProperty = RegisterProperty(p => p.ReturnId);
public int ReturnId
{
   get return ReadProperty(ReturnIDProperty);
   private set LoadProperty(ReturnIDProperty, value);
}

As in your DataPortal_Execute you try to get the unmanaged field value.
- the recommended way is to use managed properties.


The error message you get simply says that the BusinessObject has broken rules and is not valid for save.

You should inspect <BusinessObject>.BrokenRulesCollection to see which rules are broken for which fields and redirect to a new page to show the errors.

Do you use the CslaModelBinder (assuming that this is an ASP.NET MVC application)?

Chris62 replied on Thursday, February 16, 2012

Ok, I fixed most of this.  (Sorry for the delay, got pulled off for a prod issue).  Currently I write out to a file with any broken rules in it, and so far there are none(after fixing some table perms), yet .IsValid, and .IsSaveable are both testing false (.IsSelfValid is true) for the parent object, while BrokenRulesCollection.Count is 0.  

The parent may or may not be creating children based on properties being set(example: if SomePropertyId = 5, then create a child object using a property rule).

We're not using CslaModelBinder, this is a Survey website, the questions/answers will be different for each survey.

I put a couple of lines of code in the DataPortal_Insert() to write a line to a file on the server, and we're not getting to the server(probably the IsSaveable and/or IsValid being false).

protected override void DataPortal_Insert()
{
TextWriter here = new StreamWriter("C:\\dp_insert.txt", true);
here.WriteLine("insert");
here.Close();
// normal insert code here

Hopefully I haven't confused you Big Smile, and thanks for your help!!!

JonnyBee replied on Thursday, February 16, 2012

Use BrokenRules.GetAllBrokenRules method to get a list of all objects in you "tree" that has broken rules.

When IsSelfValid is true and IsValid is false then you have broken rules on the child objects and the object cannot be saved.

Chris62 replied on Friday, February 17, 2012

The GetAllBrokenRules really helped out.  From there I was able to dig through and locate the error(I missed setting a permission on a table).  It's now up and running.

 

Thanks again for your help(and patience)!!!

Chris62 replied on Tuesday, February 14, 2012

I haven't been able to find any other error messages.  Either in the Event Viewer or the WCF trace log(turned on through web.config/system.diagnostics).

Copyright (c) Marimer LLC