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).
Here's the stack trace:
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
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.
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.
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
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)?
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 , and thanks for your help!!!
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.
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)!!!
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