How to fire up rules on a LIST ?

How to fire up rules on a LIST ?

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


Lord02 posted on Monday, June 07, 2010

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

RockfordLhotka replied on Monday, June 07, 2010

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.

bniemyjski replied on Monday, June 07, 2010

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

Lord02 replied on Tuesday, June 08, 2010

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

RockfordLhotka replied on Tuesday, June 08, 2010

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.

Lord02 replied on Tuesday, June 08, 2010

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

 

 

Lord02 replied on Tuesday, June 08, 2010

02 - With_CheckRules.png

Lord02 replied on Tuesday, June 08, 2010

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 );
            }
        }


Lord02 replied on Tuesday, June 08, 2010

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