Code which calls itself

I have a table which contains rules that are to be applied. Because of the nature of the rules, the order in which the rules are applied are important. Therefore the table has a non-primary key integer field which defines the order. Suppose the user then changes the processing order by changing the record’s order value to one which already exists elsewhere in the table. Thus everything in the table needs to be adjusted downward. I have this code (recursively calling itself) which does the trick. Rec is “Import Change Rule” CheckOrder() ChangeRule.SETRANGE(“Processing Order”,“Processing Order”); ChangeRule.SETFILTER(“No.”,’<>%1’,“No.”); IF ChangeRule.FIND(’<>=’) THEN BEGIN IF NOT CONFIRM(‘There is already a rule in order position %1.’+ ‘Would you like to insert this rule and push down subsequent rules?’, FALSE,“Processing Order”) THEN EXIT ELSE PushRule(Rec); END; PushRule(PushingRule : Record “Import Change Rule”)PushedRule.SETRANGE(“Processing Order”,PushingRule.“Processing Order”); PushedRule.SETFILTER(“No.”,’<>%1’,PushingRule.“No.”); IF PushedRule.FIND(’-’) THEN BEGIN PushedRule.“Processing Order” := PushedRule.“Processing Order” + 1; PushedRule.MODIFY; PushRule(PushedRule); END; (Sorry that the code doesn’t seem to want to indent here). Basically it’s like dominos that keep hitting another domino until there is nothing left to hit. The logic works okay, but my question is this: the field on which the user changed the initial record has a CurrForm.update(TRUE) on the field control of the form so that the form will re-order itself upon validation (AfterValidation). I receive an error message saying (“Another User Has Modified …”) referring to the record the user originally modified. But my code never modifies the one that the user-hand typed! Why the clash?

If I understand correctly, you have a sequential number that determines the order of your records (rules), but the user may want to insert a record between 2 other records. Instead of changing all the other records (which may take a while), did you consider having a gap of 10000 between each integer and simply insert in the middle of 2 numbers in your scenario? If you integer is a part of the key, Navision will handle that automatically by setting the autosplitkey property to Yes in a Form(see General Journal). Doing your process this way, you won’t need this recursive function or any and you shouldn’t have problems with the updates.

If you look at the bottom left of this window, you can click Forum Code. It will tell you how to include some code with indentation in the forum: [ code ] [ /code ]

I thought about using the AutoInsert of the form (implied by the 10000) number. However, the issue only arose when I tested the programs and found that they didn’t work properly because the rules are in the wrong order. It would thus be more natural for a user to change the order number of an existing rule, than to delete and re-insert elsewhere. I’ll think about it some more. Thanks.

Also, be aware that recursive functions in C/SIDE can only be called a few hundred times before a stack overflow error is raised. This can have an important impact on the desired functionality if the number of “Rules” is expected to reach the hundreds too. More info: http://www.mbsonline.org/forum/topic.asp?TOPIC_ID=9226

If you want the AutoSplitKey to work properly, you need to:

  • Have your integer number to be the last value of your primary key
  • Make sure the sorting in your form is done on the primary key
  • Switch the AutoSplitKey property to Yes on your form

quote:


Originally posted by JRN
I thought about using the AutoInsert of the form (implied by the 10000) number. However, the issue only arose when I tested the programs and found that they didn’t work properly because the rules are in the wrong order.


Thanks all for the input. I especially appreciate knowing the recursive limitations. This type of code is my preference to use when I have situations where subsequent records need to be “pushed down”, usually with respect to date or time. I use it extensively when writing scheduling applications much like MS Project (e.g., if this task is now going to take longer, all subsequent tasks need to be pushed to a later time). My original question had to do with the CurrForm.UPDATE function. My current understanding is that this function (when save record = true) is that the form writes the current version of the record, assuming that the version hadn’t changed since the record was retrieved (in which case you get a version error). The data look like this before the user edits them (that’s 2 integer fields No., and Order). No. / Order 1 / 1 2 / 2 3 / 3 4 / 4 5 / 5 6 / 6 7 / 7 8 / 8 The user then decides that rule #8 should be placed chronologically immediately after rule #4. So he changes the “Processing Order” on record #8 from “8” to “5”. There is obviously already a “5” , so the “Push Rule” function in my code has one record which is causing the pushing (the “PushingRule”), and one which is getting pushed (the “PushedRule”). At each iteration, it calls itself again so that what was just “pushed” becomes the “pusher”. This happens recursively until there are no more conflicts. You can see from this process that record #8 was never modified by the code. It was only modified by the user using the form. And yet when C/SIDE encounters CurrForm.UPDATE(TRUE), it issues a version error (“Another user has modified record #8…since you last retrieved…”). What I do not understand is why record #8 seems to be the culprit, when it it doesn’t appear to have been modified except by the user. Any thoughts?

Not giving it much thought (I admit), I would say that the changes made by the user using the Form are not yet committed to the database when your function kicks in. Something like:

    1. User changes the Order field from 8 to 5 (change not yet committed);
    1. Validation of this field (before it is actually changed) calls the recursive function;
    1. Function reads and modifies records from the database;
    1. The record with order 8 still exists in the database and is therefore modified by the function (order 8 would then become 9?!? not sure…);
    1. After field validations are finished the field is updated with the new value assigned by the user (order 8 becomes 5);
    1. The OnAfterValidate trigger calls a CurrForm.UPDATE(TRUE) which will try to save the record;
    1. The database management system sees that the record in the database no longer has the same timestamp as the one originally presented to the user;
    1. A concurrency error is raised.

If you walk your recursion very carefully using your example, I believe that you will find that when No. = 7 is ‘pushed’ to Order = 8, the next recursion will ‘find’ No. = 8, push it to Order = 9, and MODIFY it… I think you need to call MODIFY first on No. = 8, Order = 5 after the CONFIRM, before the initial call to PushRule…No?

To avoid the limitations of recursion (and possibly avoid the problem of scribbling on yourself), you might consider modifying PushRule as follows: PROCEDURE PushRule(PushingRule : Record "Import Change Rule") BEGIN PushedRule.SETFILTER("Processing Order",'%1..',PushingRule."Processing Order"); PushedRule.SETFILTER("No.",'<>%1',PushingRule."No."); IF PushedRule.FIND('-') THEN REPEAT PushedRule."Processing Order" := PushedRule."Processing Order" + 1; PushedRule.MODIFY; UNTIL PushedRule.NEXT = 0; END;