Hi there,
Can you tell me how I can force rules to be explicitly run after loading data with a CSLA List ?
The CSLA entity is called "Status" and then I have "StatusList" ( BusinessListBase< StatusList, Status > ) ... the StatusList is populating itself
by this:
this.LoadProperty( _currentStatusListProperty, RecoveryPlanner.Business.StatusList.GetByStatusID(2));
but I notice the businessRules ( validation rules) are NOT being run ( they're positioned in the Status CSLA class ... not the StatusList )
How can I explictly force the rules to be run after filling the list ?
the list never calls anything like FieldManager.XXXXX to use the Status CSLA ( which WOULD run the rules ... but I don't want to make the Status do anything at this point ... I only want the List to populate itself, and run the rules to check it's own data )
This is how I populate the list:
private void DataPortal_Fetch(StatusCriteria criteria)
{
bool cancel = false;
OnFetching(criteria, ref cancel);
if (cancel) return;
RaiseListChangedEvents = false;
using (SqlConnection connection = new SqlConnection(ADOHelper.ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("[dbo].[rp_Status_Select]", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(ADOHelper.SqlParameters(criteria.StateBag));
command.Parameters.AddWithValue("@StatusNameHasValue", criteria.StatusNameHasValue);
using(var reader = new SafeDataReader(command.ExecuteReader()))
{
if(reader.Read())
{
do
{
this.Add(new Status(reader));
} while(reader.Read());
}
else
throw new Exception(string.Format("The record was not found in 'Status' using the following criteria: {0}.", criteria));
}
}
}
RaiseListChangedEvents = true;
OnFetched();
}
//TODO: how do I run the rules for the list, after it has populated the data ?
This is the rule I want to call, after populating the list .... to verify the initial state of the data:
private static bool AllPriorTasksToMilestoneMustBeClosed<T>(T target, Csla.Validation.RuleArgs e) where T : Status
( it IS working if I change data and stuff, but I need it to run at my initial load of the list )
regards,
EE
The rules are in the child objects contained in the list. So if you want to force the rules to run, you need to call CheckRules() in each child.
This is often done in the DataPortal_Fetch() of the child - that's the normal solution.
If you don't want the rules to run until the list is fully populated (due to sibling-level rules in the child objects) then you'll need to implement an internal method in the child class, so the list can loop through the children to tell each child to run its rules.
Hello,
Since you are loading this from the database this should be valid since you have database intergrity (hopefully). You have two places that you could call CheckRules() on. You could call them on OnFetched() or OnMapped() since you are using the CodeSmith templates. You could also create and call a method called BrokenRules() on the List that returns a string of the errors. Below is some code that I typed up that may not compile.
private string BrokenRules() {
var error = new StringBuilder();
foreach(var item in this.Items){
error.AppendLine(string.Format("Item {0} contains the following broken rules {1}", item.Name, item.BrokenRulesCollection.ToString());
}
return error.ToString();
}
Thanks
-Blake Niemyjski
Hi,
The LIST is not using it's children to populate itself ( hence, I'm not running through the DataPortal_Fetch() inside the *children* )
- How do YOU populate a list ? Do you call fetch on the child for each and every item/child ( so it will be multiple calls to the database, instead of just ONE call to the database by populating it in the Fetch of the LIST ? )
I guess I have to create some internal function to loop through all the children and check if they're valid or not ( CheckRules )
p.s. why am I not getting emails when you post answer to this thread ? I've checked the "Email me replies to this post" ?
rgd,
EE
You might need to go into your profile and give the forum permission to send you emails, I'm not sure.
You should read chapters 17 and 18 of Expert Business Objects - they cover various data access concerns and techniques.
I typically have the list do the database query, and then loop through the results (through the datareader or entity list or whatever), calling DataPortal.ChildFetch() for each child - passing the appropriate row of data or entity so the child can populate itself. This is the model the data portal is designed to support.
OK, now I got the email ( did not change anything in my forums settings )
I'm already doing it like that, that is ... I make the Child create itselfWith this ( marked BOLD ):
private void DataPortal_Fetch(StatusCriteria criteria)
{
bool cancel = false;
OnFetching(criteria, ref cancel);
if (cancel) return;
RaiseListChangedEvents = false;
using (SqlConnection connection = new SqlConnection(ADOHelper.ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("[dbo].[rp_Status_Select]", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(ADOHelper.SqlParameters(criteria.StateBag));
command.Parameters.AddWithValue("@StatusNameHasValue", criteria.StatusNameHasValue);
using(var reader = new SafeDataReader(command.ExecuteReader()))
{
if(reader.Read())
{
do
{
this.Add(new Status(reader));
} while(reader.Read());
}
else
throw new Exception(string.Format("The record was not found in 'Status' using the following criteria: {0}.", criteria));
}
}
}
RaiseListChangedEvents = true;
OnFetched();
}
And then I added this in the child ( the Status ... of StatusList ):
partial void OnMapped()
{
this.ValidationRules.CheckRules();
}
but then the Status will not continue loading it's children ... I'll attach image to this post
Normal 0 21 false false false IS X-NONE X-NONE MicrosoftInternetExplorer4
I need to be able to call the CheckRules AFTER
all the data has been loaded into the Grid …
Normal 0 21 false false false IS X-NONE X-NONE MicrosoftInternetExplorer4
This is how the data looks like:
01 - Without_CheckRules.png
Normal 0 21 false false false IS X-NONE X-NONE MicrosoftInternetExplorer4
But if I run this in the Status class ( the child )
partial void OnMapped()
{
this.ValidationRules.CheckRules();
}
The data will only look like this:
and then 02 - With_CheckRules.png
Normal 0 21 false false false IS X-NONE X-NONE MicrosoftInternetExplorer4
This is the rule I want to run … it checks the „CanCloseMileStoneTask“ ( the column on the right … which will be hidden when released, only for debug now … it controls the „Closed“ Property )
- I need this rule which runs through all the Tasks and marks if the user can close the task or not.-
protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)
{
//todo: prófaði að færa hingað:
//base.OnChildChanged(e);
ProjectTask gaur = e.ChildObject as ProjectTask;
if (gaur != null)
{
//if( gaur.MileStone == true )
//e.ListChangedArgs.
CurrentProjectTask = gaur;
this.ValidationRules.CheckRules("StatusID");
//TODO: prófaði að bæta þessu inn:
//OnPropertyChanged("StatusID"); // http://forums.lhotka.net/forums/p/9032/42960.aspx#42960
CurrentProjectTask = null;
}
//á að vera hérna:
base.OnChildChanged(e);
}
protected bool AddBusinessValidationRules()
{
ValidationRules.AddRule<Status>(AllPriorTasksToMilestoneMustBeClosed<Status>, "StatusID");
return true;
}
private bool isRunningAllPriorTasksToMilestoneMustBeClosed = false;
private static bool AllPriorTasksToMilestoneMustBeClosed<T>(T target, Csla.Validation.RuleArgs e) where T : Status
{
if (target.isRunningAllPriorTasksToMilestoneMustBeClosed == true)
{
return true;
}
target.isRunningAllPriorTasksToMilestoneMustBeClosed = true;
//int numberOfClosedTasks = 0;
// # Rule number one: Never talk about Fight club - Regla sem kemur í veg fyrir að hægt sé að vista MileStone ef önnur tösk innan sama phase er ekki lokið
// þarf að vera hægt að toggla "CanSaveMilestone" fram og aftur eftir því sem töskin innan phasans breytast.-
// passa að uppfæra líka estimatedDags á VÖRÐUM á sama dags. og Task með hæstu dags. ( er þetta ekki gert nú þegar niðrí Phase reglunum ? )
//Regla #1 þarf væntanlega að keyra strax í byrjun, ekki bara í OnChildChanged ( því það verður að vera hægt að loka Vörðu STRAX í upphafi forms ):
if (target.CurrentProjectTask != null)
{
RuleNumberOne<T>(target, target.CurrentProjectTask.PhaseID);
}// # Rule number one ENDS
//TODO: Ef fasi er opnaður eftir að honum hefur verið lokað þá sjálfkrafa þarf að opna fasana sem á eftir koma,
// Þannig yrði farið og tekið af hakið í close, á vörðum fasanna sem koma á eftir
// # Rule number two Begins:
var allClosedMilestones = from p in target.Phases
from c in p.ProjectTasks
where c.MileStone == true
where c.Closed == true
select c;
foreach (var closedMilestone in allClosedMilestones)
{
var allLesserNotClosed = from p in target.Phases
from c in p.ProjectTasks
where c.MileStone == true
where c.TaskID < closedMilestone.TaskID
where c.Closed == false
select c;
if (allLesserNotClosed.Count() > 0)
{
closedMilestone.CanCloseMilestoneTask = false;
}
else
{
closedMilestone.CanCloseMilestoneTask = true;
RuleNumberOne<T>(target, closedMilestone.PhaseID);
}
}
// # Rule number two Ends
target.isRunningAllPriorTasksToMilestoneMustBeClosed = false;
return true;
}
private static void RuleNumberOne<T>(T target, int phaseId) where T : Status
{
var milestones = from p in target.Phases
from c in p.ProjectTasks
where p.PhaseID == phaseId
where c.MileStone == true
select c;
//var milestones = from m in target.CurrentProjectTask.PhaseID ProjectTasks
// where m.MileStone == true
// select m;
var countNotClosed = (from p in target.Phases
from c in p.ProjectTasks
where p.PhaseID == phaseId
where c.MileStone == false
where c.Closed == false
select c).Count();
var countAll = (from p in target.Phases
from c in p.ProjectTasks
where p.PhaseID == phaseId
where c.MileStone == false
select c).Count();
//TODO: fæ villu ef ég er að setja inn vörðu í Phase þar sem engin tösk eru !!!!! - hugsanlega hverfur þessi villa þegar það verður komið amk eitt MileStone fyrir hvert phase
// eftir nýskráningu Task í gegnum Project
if (milestones != null)
{
//hér kemur NULL reference villa þegar keyrt er inn í 2nd skiptið
//sem er að valda öllu ruglinu:
var coll = new List<ProjectTask>();
foreach (var milestone in milestones)
{
coll.Add(milestone);
}
foreach (var milestone in coll)
{
milestone.CanCloseMilestoneTask = (countAll == 0) || (countNotClosed == 0);
}
}
}
#endregion
02 - With_CheckRules.png
How would you do this, and more importanlty WHERE would you call this rule ?
"If you don't want the rules to run until the list is fully populated (due to sibling-level rules in the child objects) then you'll need to implement an internal method in the child class, so the list can loop through the children to tell each child to run its rules."
I was thinking of something like this:
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "SelectedProjectID")
{
RecoveryPlanner.Business.ProjectTaskList.ProjectIDToFetchFrom = SelectedProjectID;
//Only get Status: Recovery ( which has StatusID: 2 )
this.LoadProperty( _currentStatusListProperty, RecoveryPlanner.Business.StatusList.GetByStatusID(2));
//TODO: if I change something in the status, it will probably fire up the rule ????
foreach (Status item in CurrentStatusList)
{
//TODO: loop through each item and check it's rule ?
item.CheckXXXXX // Not availble ...
}
this.LoadProperty( _steeringCommitteeForSelectedProjectProperty,RecoveryPlanner.Business.SteeringCommitteeList.GetByProjectId(SelectedProjectID));
this.LoadProperty( _statusLastUpdatedProperty, RecoveryPlanner.Business.ProjectTaskHistoryInfo.GetByProjectID(SelectedProjectID).DatePerformed );
}
}
I've completed this successfully:
Created an internal function to run AFTER the whole list has been populated.-
See my solution:
Normal 0 21 false false false IS X-NONE X-NONE MicrosoftInternetExplorer4
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
if (propertyName != "CanSave")
{
if (CurrentStatusList != null)
CanSave = this.IsSavable && CurrentStatusList.IsSavable;
}
if (propertyName == "SelectedProjectID")
{
RecoveryPlanner.Business.ProjectTaskList.ProjectIDToFetchFrom = SelectedProjectID;
//Only get Status: Recovery ( which has StatusID: 2 )
this.LoadProperty( _currentStatusListProperty, RecoveryPlanner.Business.StatusList.GetByStatusID(2));
foreach (Status item in CurrentStatusList)
{
//TODO: loop through each item and check it's rule, through the new internal function I added:
item.CheckForBrokenStatuses();
}
this.LoadProperty( _steeringCommitteeForSelectedProjectProperty,RecoveryPlanner.Business.SteeringCommitteeList.GetByProjectId(SelectedProjectID));
this.LoadProperty( _statusLastUpdatedProperty, RecoveryPlanner.Business.ProjectTaskHistoryInfo.GetByProjectID(SelectedProjectID).DatePerformed );
}
}
In status ( the Child ) I added this:
internal void CheckForBrokenStatuses()
{
this.ValidationRules.CheckRules();
}
Thanx Lhotka !
EE
Copyright (c) Marimer LLC