Shortest method to know all fields updated by another user (in Table Buffering mode)
Shortest method to know all fields updated by another user (in Table Buffering mode)
(OP)
Dear all,
When the current user is on a particular record, what is the shortest method to identify all the fields that were updated by another user?
The table is in Optimistic Table Buffering at both ends. However, this is regarding only a single record.
Rajesh
When the current user is on a particular record, what is the shortest method to identify all the fields that were updated by another user?
The table is in Optimistic Table Buffering at both ends. However, this is regarding only a single record.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
GETFLDSTATE() only checks the current user session, isn't it?
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
You would need to get OLDVAL() and CURVAL() for every field that is marked as modified by GETFLDSTATE().
The changed fields will be updated. If their CURVAL('field','alias') differs from OLDVAL('field','alias'), CURVAL is what another user already saved into the DBF, meanwhile. And that's a recent change you would overwrite with alias.field of your user and would thereby cause a conflict. I think, by the way, even if the value your user entered is the same as CURVAL('field','alias'), that still causes a conflict. Imagine code that should increment a field for any change, then the final value should be oldval+2, if two users edit the record. If both CURVAL and your users' value is oldval+1, that's saying both incremented based on the same OLDVAL, and the final value would only have an increment of 1 instead of 2. That's still worth calling a conflict.
The usual thing to do is try TABLEUPDATE, if there is a conflict identify it, i.e. for each field check GETFLDSTATE('field','alias')>2, and if so look out whether CURVAL('field','alias') differs from OLDVAL('field','alias'). To avoid a conflict by that field, you can use SETFLDSTATE('field',1,'alias') to tell VFP to ignore your users' change to not save that new value and respect the CURVAL('field','alias') from the other user. It's not a solution to just set alias.field to CURVAL('field','alias') to overwrite the other user value with the same value.
There is a corner case if another user deleted the record you want to update or you delete a record another user changed from the old state you initially loaded, that's also causing a GETFLDSATE of 2 (or 4, but that's only for new rows you delete before even saving and can't cause a conflict). Because, of course, deleting a recent change is judged as a conflict. "recent" is not a matter of time, but of OLDVAL differing from CURVAL. If your user started editing a record yesterday and wants to finally save today, that's of course much more likely means that CURVALs already changed.
Well, and the way to write your users' changes over the CURVALs is to use the lForce parameter of TABLEUPDATE set to .T., which turns off conflict checking. If you always use this, you never detect which values you overwrite, though.
I haven't given you the "Shortest method to know all fields updated by another user (in Table Buffering mode)", because that's not what you need. You only need the CURVALSs of fields your user modified. but on top of the current values you also need to know the OLDVAL differs from CURVAL, so CURVAL is actually a change of the other user. You can never reach into the buffer of another user to see what he currently wants to store, only what already is stored.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
OLDVAL('field','alias') is the value your users' editing started with, it's what you loaded and a value VFP keeps in mind, your changes are not affecting this, as they are written inot the buffer, not yet into that field of the DBF. But OLDVAL() is not just reading the value from the DBF, it's what VFP remembers to have initially loaded.
CURVAL('field','alias') is what is currently stored in the DBF. This is really reading from the DBF. If that's the same as OLDVAL('field','alias'), there's no change from your users' sessions perspective, which could potentially be in conflict with what you want to store.
ALIAS.FIELD is the buffered new value of your user, if he changed it. If not, it's the same as OLDVAL('field','alias'). It would then not cause a conflict, even if curval changed, as you don't intend to update the DBF with an unchanged ALIAS.FIELD. Here you see why VFP keeps track of GETFLDSATE apart from knowing the value of a field, to also know whether it changed status.
The three values OLDVAL, CURVAL and FIELD can all differ, but remember even if CURVAL and FIELD are the same, that's still considered a conflict if your local field state says it's a change. And, of course, conflicts always involve ODLVAL. If just any CURVAL<>FIELD would be judged as a conflict, that would mean any change is considered a conflict. It's only ever a conflict of status says you modified a field and CURVAL<>OLDVAL says this also has been changed since your modification started. And that can be a longer timespan, so it's not just a concurrency conflict that's indicated.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Excellent info. You're touching many aspects and scenarios!
Yes, I am using GETFLDSTATE(), SETFLDSTATE() in many places. Also, CURVAL(), OLDVAL(), but comparitively in fewer areas.
At least in my said module, deletion of a record doesn't happen, by any user. So, I am just ignoring that matter.
Now, as you said, I will need to check manually each and every field of participating data tables for modified values.
(as the answer to my actual thread question)
In my case, I will need to check modification by current user as well as others as in:
If <controlObject>.Value <> OLDVAL() means current user has modified it.
If CURVAL() <> OLDVAL() means another user has modified it (maybe current user also modified it but another user's change is obvious)
Correct?
In my form, I have ONLY SOME FIELDS which are supposed to be modified it by current and other users.
(for example, like different departments modifying only fields related to their functions)
But the current main user also is allowed to modify these fields.
So, for any of these fields,
If the current user HAS NOT modified it:
- if there is a change by another user, I will take that value for final saving.
If ONLY the current user HAS modified it:
- I will take that value for final saving
If BOTH current user AND another user HAVE CHANGED it:
- I will ask the current user which value to save
This is what I am thinking for my scenario.
Then before saving, I will update current user form control values according to the above rules.
Then in final save I will TABLEUPDATE using the FORCE parameter.
Let me test/check this case by case.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
- OLDVAL() tells you the value of a field at the point where you started to edit it (or, more exactly, at the point where it entered the buffer in your program).
- CURVAL() tells you it current value on disk, as updated by another user.
- And the value of the field itself is its value as it exists in your buffer.
So if, for a given field OLDVAL() is different from CURVAL(), then another has changed the value.
You could use code like this:
CODE -->
Of course, this only works if buffering is in force. And it only applies to optimistic locking, as the situation won't arise if pessimistic locking is used.
Mike
__________________________________
Mike Lewis (Edinburgh, Scotland)
Visual FoxPro articles, tips and downloads
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Yes, as said in my previous post, I am going to do in that line, similar. But, I will need to go a bit more elaborate, as current user may want to override other user's change and force his/her value.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
2. Yes.
You won't take anything like that, the TABLEUPDATE will not change those fields your user didn't modify, so they are out of question anyway, they simply stay at CURVAL, what's currently in the DBF, no matter if that's old or a recent change by any other user and no matter if your value still is OLDVAL. The situation a second user still has buffered changes is taken care of when he saves, not right now. If he already saved, that's just CURVAL and you don't make CURVAL your users value, that's asking for trouble, that's causing a conflict by intention. So DON'T take that value for your saving. If by SETFLDSTATE there is no change in a field, it won't matter at all what's in FIELD, it's not written back at all. It's a skipped field of the SQL-UPDATE Tableupdate8) produces.
I wonder if you don't use TABLEUPDATE but simply compose an UPDATE-SQL yourself or use a standard UPDATE of the fields your user may modify and parameterize it. If so, why?
This needs no consideration, TABLEUPDATE does save that.
Yes, that's what to consider for conflict management.
You can present CURVAL() to your user as the change that has been made by another user. If you detect that CURVAL('FIELD')==FIELD you could also use SETFLDSTATE to suppress this change of your user and not bother your user with the fact that another one already made the same change. That is, if you know this can be valid and don't have that increment scenario where you would generally want to save CURVAL+1 instead of OLDVAL+1. The usual scenario will be that CURVAL and FIELD differ, so you display that and could ask your user, if he wants to undo his change and instead accept the CURVAL, then use SETFLDSTATE, too, to put it into state 1, which means unchanged. If your user would still specifies his change is what should be stored you can save it with the lForce option.
The only downside of the lForce option is that you can't make it an option for single fields, you either force it for all changed fields of your user or not. For catering that you could accept the CURVAL by actually copying them to the changed fields and use the lForce=.T. option to resave the values that already are CURVAL and keep them that way. So if you have conflicts in multiple fields the copying of some CURVAL values into the buffer makes sense to accept some and not other values and then resave them with the changes your user wants to overwrite alltogether with lForce=.T.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Let's consider a simple scenario: You loaded two fields (id, firstname, and lastname) with values (10534,'Rajeesh', and 'Karunkaran'), those are the OLDVALs and in control.value bound to fields those are kept that way, if you don't change them.
Another user corrected the lastname from 'Karunkaran' to 'Karunakaran'. But your form still displays the wrong value 'Karunkaran', not CURVAL. In fact your control would update to the corrected value, if you set focus to it. But it keeps the OLDVAL display unless you do so.
You correct your firstname from 'Rajeesh' to 'Rajesh' and save. Now TABLEUPDATE does not overwrite the CURVAL of the lastname 'Karunakaran' with your OLDVAL 'Karunkaran', as you only changed firstname, TABLEUPDATE produces an UPDATE-SQL that will look like this:
CODE
If somebody had also changed the firstname, this UPDATE SQL would cause a _TALLY of 0 and that's the conflict detection. But those two changes are conflict free, as they are modifiying separate different fields, each.
So, TABLEUPDATE is only caring for the modified fields, it is not a mechanism that takes the full record as you have it and saves that. You only need to take care of CURVAL of modified fields in your session. Nothing else. That's why GETFLDSATE is important, not just FIELD, OLDVAL('FIELD') and CURVAL('FIELD'). You don't need to protect CURVAL vvalues if you didn't touch fields. Unless you put together the SQL yourself and do such things as having a general SET field1=value1, field2=value2,...., fieldN =valuen that would take whatever is the current value in your form. But then again, I ask: Why would you do that? You use TABLEUPDATE() to update a DBF, or also a remote database table.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Rajesh, this is surely the most important point.
When two users are trying to edit the same record at the same time, you have to consider some different cases:
- The two users are editing different fields. In this case, you will read the CURVAL() of the relevant fields into your buffer, and then commit the entire record.
- The two users are making the same edits to the same fields. (This can happen, for example, if a customer sends in a change-of-address notification, and for some reason two different users pick it up.) No special action is needed in this case.
- The two updates are cumulative. For example, where two users are selling the same product, the first user might increase the sales total by 100 units, and the second user by 200 units. So you need to add the CURVAL() (100 in this case) to the value in your own buffer (200), and use the record with the resulting figure (300).
- The two updates collide. For example, in an airline reservation system, both users start selling a ticket for the last seat on a given flight. The user who commits their update first will successfully sell the seat. By the time the other user comes to commit the record, their customer's seat no longer exists. What to do? Sell the seat anyway, and risk overbooking? Tell the customer that they can't have the seat after all? Overwrite the first user's booking with your own?
The point about all this is that it is not a technical issue. It is a business decision. Whenever you do optimistic locking, you have to take into account the business rules that govern your updating.
Of course, you can avoid all this by using pessimistic locking, but that brings problems of its own.
Mike
__________________________________
Mike Lewis (Edinburgh, Scotland)
Visual FoxPro articles, tips and downloads
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
I beg to differ here, as I explained in length already. That's a case causing no conflict. Even worse, if you load the CURVAL into your buffer and thereby set the field state to "changed", you want to save them and that will be detected as a conflict, if OLDVAL differs from these CURVALs. And if OLDVAL doesn't differ you wouldn't load them into your buffer. So that's actually causing the problem yourself. If you mean to commit the entire record with lForce=.t., that would work, but just cause some unnecessary overwrites of values with themselves.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
I see that, the scenarios abouts CURVAL() being taken into consideration for a field not updated by current user is highly depended on whether the TABLEUPDATE() is issued with the Force parameter.
I see it as follows:
Suppose, I read the record and updated only firstname. Another user updated Lastname but my screen shows the old lastname value (obviosly). I did not touch anything else and go for saving. Now, if my TABLEUPDATE() is not with Force parameter, it gives a conflict error and doesn't update anything. If I use Force parameter, I will loose the lastname value updated by another user and will save the old value.
So, if I need to save my value and the new value from another user (on lastname which I didn't touch), I will need to go for CURVAL() of lastname, update it onto my buffer and then go for TABLEUPDATE(). This is what Mike was saying I believe.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Then, if current user doesn't want to force his value (this is in fact actually to be validated for each such field to make it really perfect), I will take values from other users, update my buffers and then go for TABLEUPDATE().
I will keep my TABLEUPDATE() always with FORCE=.T.
so that I don't need to keep two TABLEUPDATE()s conditionally.
These are my current thoughts. There are obviously many routes to achieve something.
So, I would certainly like to know, if we're able to think of other way(s) to handle this scenario?
Maybe a better, simpler and more rigidly maintainable way.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
That analysis is correct. My solution would be to use the code I showed above to look for a changed field. If that field is not the one you are editing, use CURVAL() to retrieve its new value. This could be a simple as:
REPLACE TheField WITH CURVAL("TheField")
However, I think Chris disagrees with that. I'll need to real Chris's post more carefully.
NO. That's the last thing you should do. In almost all cases, the user won't have enough information to make that decision, and probably won't understand the implications of doing so. In any case, if you say to the user: "Something has gone wrong; do you want to do something that results in you losing the work you have just done?", the chances are that they will always say no to that. This is why you need business rules to guide you in these cases.
Mike
__________________________________
Mike Lewis (Edinburgh, Scotland)
Visual FoxPro articles, tips and downloads
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Your "investigation" only begins with your own users changes. They can only conflict with values in the DBF in these changed fields, not in all. Collisions can only happen with what you changed according to GETFLDSTATE. No more. So as I already initially said, you want to know too much. You don't need to know about all changes of other users, not even in the record you handle currently. It doesn't matter if curVAL and OLDVAL differ in fields that are unchanged according to your own GETFLDSTATE, those are untouched, you don''t write back the OLDVAL, if your userr didn't touch that field value, you just don't handle that field at all.
So, all in all you only need to care about the fields that are changed and submitted by your user. And for these fields it is interesting to know whether CURVAL and OLDVAL differ, as that means these fields were not only changed by your user, but other users as well, they even already saved - managed to save - those changes without conflict. It doesn't even matter if it were already two or three changes by other users.
I and Mike even differ in what we think happens. You seem to be afraid that you overwrite fields with their old value, that were changed by other users, even though your user didn't change them. But that doesn't happen. TABLEUPDATE skips fields your user didn't touch, so there is nothing you need to protect in these fields. You only care about the fields your user wants to change right now and which also one or many other users already changed in the DBF from OLDVAL to CURVAL. When you're finished with saving your buffer is empty. The fields' CURVAL is what you just saved and becomes OLDVAL if you allow the editing session to continue. So, just as a side note, it can prevent collisions for your user, if he saves more often than just at the end of all his editing.
But the basis of your conflict detection is just fields that GETFLDSTATE reports as changed by your user. It only reports changes of your user, not of others, as it's about your users alias/workarea and buffer, locally. You don't detect what fields other users changed with GETFLDSTATE. You detect that by the difference in OLDVAL vs CURVAL. Notice that CURVAL is not your users' current value, it's the current value of a field in the DBF file. In your workarea, FIELD reads either the buffered change or OLDVAL, if there was no change by your user in your workarea. The OLDVAL won't matter if this field is not having the status of being changed, as TABELUPDATE then skips it in his UPDATE statement. You don't need to be afraid of overwriting changes other users already saved with OLDVALs, the overwriting can only happen, if your user also modified the OLDVAL he read to something new. Please read about GETFLDSTATE() in the help file.
Notice also, that if you insert a record into a buffered table, this record is not yet in the DBF at all before TABLEUPDATE, there will be no other users conflicts with such records, so we only need to consider talking about records you initially loaded into your form, for example simply by binding controls to fields that read at least one record. Only those already existing records you modify can even have conflicts with other users' changes. Only those records have a CURVAL in the DBF at all.
Maybe you also didn't yet get that if you turn on buffering, the changes a user makes don't go into the DBF directly, Your changes are buffered, thats why this is caled buffering at all. You only plan to save those values. The only way such a buffered value automatically is stored to the DBF is when you only use record buffering and skip from a changed record to the next. Because the buffer is not meant to have a second record buffered, too, you then implicitly do a TABLEUPDATE without code. And you can get errors with conflicts, then, which you then can't handle as gracefully as keeping full control about the point in time and code for saving with TABLEUPDATE.
No, if your user didn't change fields it's of no interest what other users did to them, they are not touched by your user saving his changes, also not with lForce=.T.
Let's just look at the exact case where you need to act: You find out your user has changed field "FIRSTNAME" by GETFLDSATE. You find out by comparing CURVAL("FIRSTNAME") and OLDVAL("FIRSTNAME") that another user changed the value, too. You can't find that out just by comparing CURVAL("FIRSTNAME") with FIRSTNAME itself, that just will tell you your user changed FIRSTNAME and you know that already, as you found out by GETFLDSATE() that your user changed this field. You know that without knowing ODVAL or CURVAL, as VFP just keeps track of this state of the field. States of a field can be 1- loaded and unchanged, 2 loaded and modified, 3 new and at the initial default value or 4 new and modified from the initial default value. This is part of the buffer information VFP keeps up to date no matter with which commands or controls you modify a workarea. To conclude this situation:
1. your user changed FIRSTNAME from OLDVAL to something buffered you want to save now. Another user (or even many already) changed that field FIRSTNAME from OLDVAL("FIRSTNAME") to CURVAL("FIRSTNAME"), so these two values differ, then you now you run into a conflict if you do a TABLEUPDATE. And I recommend not to fix this by REPLACE FIRSTNAME with CURVAL("FIRSTNAME") but by overrideing the fied state. You tell VFP to not save your users change, by overrideing the field STATUS, not its value. So it is skipped in the TABLEUPDATE(), either just that field is skipped or the whole record is skipped from updating - if there are no other field changes. You don't need to be actively the savior of CURVALs by "inheriting" them into your planned change. Insteead you just exclulde your field change from the overal buffer submission.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
I am confused. My small test (I tested it again even though I already know or experienced at least in my VFP environment) doesn't show things the way you're explaining.
For example, I have a table CaseMaster with
RecId
Case_Code
PersonName
Follwoup_Date
User1 accessing the table through Form-1
User2 accessing the table through Form-2
User1 reads an existing record into his form. Suppose all fields already have values.
User1 edits PersonName from 'Rajesh' to 'John'. He didn't touch anything else.
Now, User2 modifies Followup_Date from 01/01/2023 to 02/02/2023 and SAVES it to the table.
Now, User1 is going to save this record with
SELECT CaseMaster
TABLEUPDATE()
Now, as I have not specified the Force parameter (2nd parameter of TABLEUPDATE), I will get a Update Conflict error and nothing will be saved.
Now, suppose as per business rule, I want to save User1's change on 'PersonName' and ALSO the change of 'Followup_Date' by User2,
I will need to take the CURVAL('followup_date') and do a REPLACE in User1's Buffered Table and then do TABLEUPDATE().
But here, when I did the REPLACE, the values became same but still it's a conflict and you get a Update Conflict error.
So, even after the REPLACE for all such required fields, you need to do a TABLEUPDATE as in
TABLEUPDATE(.F., .T.) && with .F. being for single record and .T. for a Forced Update as we all know.
to get the record updated correctly.
As I understand, GETFLDSTATE will only help you to identify if any of the fields in record was modified or not.
But, to achieve my data as per the business rules, I need to check the fields, update them with another user's new value (if required) and then go for a saving, yes, TABLEUPDATE with FORCING parameter. Otherwise, at least in my environment, it doesn't work the way described above.
This is what I know so far.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
This code snippet from VFP HG7 might help you understand what GetFldState does and what it does NOT do
hth
MarK
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
It turns out that in case of a conflict it does neither help to replace fields with their CURVAL, nor, as I said override their field status with SETFLDSTATE. The only thing that avoids the conflict in a second run of TABLEUPDATE is reverting the record with TABLEREVERT(.F.) - .F. here means only revert the current record, not all changes.
The code also includes the other things tested in the lines I highlighted:
CODE
Close Tables All Clear Create Table Persons (id Integer Autoinc, Firstname V(32), Lastname V(32)) Insert Into Persons (Firstname, Lastname) values ('Rajeesh','Karunkaran') && errors for demonstation purposes Use * Say User 1 and User 2 start their editing sessions quite at the same time Set Multilocks On Select 0 Use Persons Alias Persons1 && workarea of User 1. Usually just named 'Persons' on his own computer CursorSetProp('Buffering',5) && buffering of workarea 'Persons1' set to optimisitc table buffering Select 0 Use Persons Alias Persons2 Again && workarea of User 2 CursorSetProp('Buffering',5) && buffering of workarea 'Persons2' set to optimisitc table buffering * User 1 edits and saves Select Persons1 Replace Lastname With 'Karunakaran' * Now the section of code of interest, how to detect possible conflicts * Step 1: Determine changed fields llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 1 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' * Step 2: Check difference in OLDVAL and CURVAL: If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' EndIf llSuccess = TableUpdate(1,.F.) ? 'User 1 lastname change success:', llSuccess ? '----------------------' * User 2 edits firstname: Select Persons2 Replace Firstname With 'Rajesh' * Now the section of code of interest, how to detect possible conflicts * Step 1: Determine changed fields llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 2 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' * Step 2: Check difference in OLDVAL and CURVAL: If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' EndIf ? 'User 2 firstname change success:', TableUpdate(1,.F.,'Persons2') ? '----------------------' Select 0 Use Persons Again ? 'Name in the DBF:', Persons.Firstname, Persons.Lastname ? 'The expected result in the line above: Both changes were saved without conflict and without force.' ? 'So the final name is "Rajesh Karunakaran".' * Now demonstrating the conflict case, User 2 tries to override the change of User 1 in lastname (still in the same session/workarea) ? ? 'Now the conflict case:' ? '----------------------' * Preparation stage * both users change the lastname from the last commonly known OLDVAL to something else Select Persons1 Replace Lastname With 'ABC' Select Persons2 Replace Lastname With 'XYZ' * User 1 saves first Select Persons1 llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 1 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' Endif llSuccess = TableUpdate(1,.F.) ? 'User 1 tableupdate success:', llSuccess ? 'Name in the DBF:', Persons.Firstname, Persons.Lastname ? '----------------------' * Now trying to save the change of the same field in Persons2 (User 2) Select Persons2 llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 2 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' Endif llSuccess = TableUpdate(1,.F.) ? 'User 2 tableupdate success:', llSuccess ? 'Name in the DBF:', Persons.Firstname, Persons.Lastname If NOT llSuccess ? 'We tried to save ignoring the known conflict, now lets accept the change of User 1...' * Replace Lastname With Curval('LASTNAME') * SetFldState('LASTNAME',1) TableRevert(.F.) EndIf * Repeat the conflict check and save routine: llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 2 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' Endif llSuccess = TableUpdate(1,.F.) ? 'User 2 tableupdate success:', llSuccess ? 'Values in the dbf:', Persons.Firstname, Persons.Lastname
In essence a general conflict check is programmed into this, not just specifically for this case:
CODE
I was very sure SETFLDSTATE() could do the necessary wonder for TABLEUPDATE to skip the field, I am still sure this works for the case of a remote database used as the source of the data and the destination of saving the buffered changes.
If only TABLEREVERT() works, this means it becomes cumbersome to revert only some changes and keep non-conflicting changes. As the first part of the code shows, two users can do just what I described earlier today with the name modification example going from ("Rajeesh","Karunkaran") to ("Rajeesh","Karunakaran") and finally to ("Rajesh","Karunakaran") in two steps, where the second one does not revert the first change. Each user only saves his modifications, not all fields.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Here is a variation of my code, that indeed finds no conflict by my check and still fails to tableupdate. I then try to bring the workarea into a state enabling the TABLEUPDATE by two steps:
1. TABLEREVERT
2. redo the same changes
For step 2 I use my conflict check code to record a list of changed felds, copy their values into a partial record object loChanged, which is finally applied.
CODE
Close Tables All Clear Create Table Persons (id Integer Autoinc, Firstname V(32), Lastname V(32)) Insert Into Persons (Firstname, Lastname) values ('Rajeesh','Karunkaran') && errors for demonstation purposes Use * Say User 1 and User 2 start their editing sessions quite at the same time Set Multilocks On Select 0 Use Persons Alias Persons1 && workarea of User 1. Usually just named 'Persons' on his own computer CursorSetProp('Buffering',5) && buffering of workarea 'Persons1' set to optimisitc table buffering Select 0 Use Persons Alias Persons2 Again && workarea of User 2 CursorSetProp('Buffering',5) && buffering of workarea 'Persons2' set to optimisitc table buffering * User 1 and User 2 edit a tthe same time Select Persons1 Replace Lastname With 'Karunakaran' Select Persons2 Replace Firstname With 'Rajesh' Select Persons1 llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 1 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' * Step 2: Check difference in OLDVAL and CURVAL: If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' EndIf llSuccess = TableUpdate(1,.F.) ? 'User 1 lastname change success:', llSuccess ? '----------------------' * Now the section of code of interest, how to detect possible conflicts * Step 1: Determine changed fields Select Persons2 llConflict = .F. lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 ? 'Status of field '+ Field(lnState-1)+' is '+Substr(lcStates,lnState,1) ? 'Oldval:'+ OldVal(Field(lnState-1)) ? 'Curval:'+ CurVal(Field(lnState-1)) ? 'User 2 changed this field value to "'+ Evaluate(Field(lnState-1))+'"' * Step 2: Check difference in OLDVAL and CURVAL: If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1)) llConflict = .T. Endif EndIf EndFor If llConflict ? 'A conflict is to be expected.' EndIf llSuccess = TableUpdate(1,.F.) ? 'User 2 first name change success:', llSuccess If NOT llSuccess * Retry after TABLEREVERT and restoring the changes: lcFieldlist = '' && list of changed fields lcStates = GetFldState(-1) For lnState = 2 to Len(lcStates) If Val(Substr(lcStates,lnState,1))>1 lcFieldlist = lcFieldlist+','+Field(lnState-1) EndIf EndFor lcFieldlist = Substr(lcFieldlist,2) If !Empty(lcFieldlist) Scatter Fields &lcFieldlist Name loChanges TableRevert(.F.) Gather Name loChanges EndIf ? 'Retry of the same change:' llSuccess = TableUpdate(1,.F.) ? 'User 2 first name change success:', llSuccess EndIf Select 0 Use Persons Again ? 'Name in the DBF:', Persons.Firstname, Persons.Lastname
The essential part is highlighted.
PS: This is not an approach to use in general when TABLEUPDATE fails. Because the TABLEREVERT() does bring your buffer to 0, you then repeat the collected changes on the basis of the current data and succeed. It's only valid to do so if the general conflict check didn't find a conflict between your changes and the current state of the DBF.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Even though the second user saving does not touch the field that causes a conflict, the CURVAL and OLDVAL of that field differ. It should not matter to VFP, as there is no task to save a buffered change of this field. I'm not sure whether I'd call this a bug or just a bad design.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Of course, I am prepared to accept that I am wrong about all this.
Mike
__________________________________
Mike Lewis (Edinburgh, Scotland)
Visual FoxPro articles, tips and downloads
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
But what I have found is that after a TABLEUPDATE() returns .F., the only way that works is making use of TABLEREVERT(), if you don't want to use the force option of TABLEUPDATE().
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Yes, you are extremely close to what I think I need to do. I have already described almost the same thing in one of earlier post.
Chriss,
You have taken a lot of effort and wrote that deeply detailed program codes.
Thanks for that. I did not go through it line by line but I have taken essentials from it.
In fact, I had made a simple form with 2-3 textboxes bound to fields and some 10-12 saving lines to see what VFP says for different scenarios and I have posted my observations in one of my earlier posts. Also, I am not planning for a generalised routine but a specific one which should match our scenarios.
I think, it's a good idea to use SETFLDSTATE, after replacing the values from other user, to set the field status as 'Not Updated'.
So, I can Replace CURVAL() wherever required, then either...
use SETFLDSTATE to reset fields to 'Not Updated' status and use TABLEUPDATE without the Force parameter to save
OR
TABLEUPDATE with the Force parameter.
Let me check which is appropriate for our case.
Mike,
Yes, you are correct. GETFLDSTATE will help in identifying only own changes and not changes by others.
Anyway, my code would be a mix of GETFLDSTATE, SETFLDSTATE, CURVAL(), OLDVAL() & TABLEUPDATE().
Even though I feel like, I have enough information now to setup my code, let's keep waiting for some more valuable ideas from our experts.
Thanks a lot everyone
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Tamar
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
I said this:
I didn't program that way around, but the whole code is just for demonstration purposes. It's also true that you still need to check for the success of TABLEUPDATE(). But also, if you found a conflict, it's very unlikely to go away.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
The idea is that you determine a list of your changes that (by current knowledge) don't conflict with any change and a list of field that do conflict:
CODE
If the final llSucess is .F. you rinse and repeat. There might be the case Tamar warned about, that a recent update from another user strikes again. You could combine all fields, but then you actually have about the same as the old case. The forced TABLEUPDATE() done in the second step should never return .F., but you never know, in that case you would have other trouble than just a collision of changes.
If the first tableupdate of nonconflicting changes already fails again, then you should not even start to force in other changes but repeat the whole process of determining the conflicts, you'll get to the forcable changes again soon enough.
In effect, this takes in all other users changes, in teh step of the TABLEREVERT(). You don't just revert to OLDVALs, you effectivly empty the buffer from the current record and make the CURVALs the OLDVALs of your reverted buffer, that is the changes of other users now become the new starting point and even the same changes then can go through fine, as in the case one user changes lastname only, the other firstname only.
So the TABLEREVERT is the core power of all of this, but it would be no good idea to skip the first try of TABLEUPDATE and always save changes to a partial record object, revert the record and then reapply the changes. Because that means they are interpreted as done from that new state and not from the original state. And that has potential for less conflicts, likely no conflicts, even though you would want to detect the actual conflicts to have the other alternative of the forced Tableupdate: to skip some of the changes. Every querstion you ask users about which value to finally keep risks the state to change again. But in a normal even 1000 users scenario not every record is changed by multiple users all the time. So you often even have the 1-5 minutes the user needs to decide for his or the already stored change.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Yes, I will be doing that way only.
Chriss,
Thank you very much for your code.
After the first Tableupdate() becomes success, there comes implementation of our Conflict business rules, isn't it?
Will make it a common function after making changes if required for suitability to our environment.
May have to add some more flexibilities as well.
Dear all,
This has been a very good discussion I feel. Came across a lot of ideas, concepts and learned of course!
Still aren't we waiting for more opinions
Thank you everyone once again
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Sure, just looking for field states different from 1 (no change) was surely not getting all the corner cases you could have to consider. It would be a good idea to "simulate" different scenarios by preparing situations of different conflicts, like changes in a record that another user already deleted. This may make it valid to recall a record and apply the changes, then. As Mike said, it all depends on the business cases, how to react, what to force in and what not. Also, interaction with the user about the problem detected might be important in some scenarios, automatic decisions are not always the best, even though time matters to not get into further conflicts.
I would estimate this is only a problem if a table is very frequently used, but even in those cases, it's questionable you have many collisions in the same record. It's true that modifications usually concentrate on the latest data, older records often are just archive material. So in the end it's not that tables with more records have less often changes of different users in the same records, in fact, their large size points out much is done with them.
Wide tables could be considered problematic to this and may point out that a 1:1 separation of fields would be a good idea. Or even a normalization process. Tables with less fields obviously also have less potential for conflicts. All in all there are certainly also many ideas to make editing the same record by different users a less likely case.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
As I understand "1:1 separation", I think, you mean to separate those fields (simultaneous multi user editable) into another table and relate it with the main table through a unique field.
We have already done this for some purposes and I will check feasibility of doing same for my current scenario.
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
If, for example, followup_date is a date of some status history of a record, it could make sense to establish that status history in a separate table or even a structure of tables.
Say, for example the main table is an order table, then there are several stages an order gets through, starting with being made by the customer through being processed, being sent and finalized. And then there can be returned orders, complaints, etc. etc. and it would be worth storing data about such events in a status history.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
I just deleted my last post. What I had written was not correct and would have created all the confusions once again afresh!
I am not sure whoever might have seen and working on it. Please ignore!
Rajesh
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
The situation of twice a buffered table on the same computer isn't unusual, though. I guess you just thought you debugged one form/datasesion, whereas you debugged the other form/datasession and so the observation was simply wrong interpretation.
It's true that doing something in the same IDE or on the same computer in two IDE session can still differ from the situaton with two separate computers, for example, because of caching effects, but usually you can test scenarios on your development computer and not need to go as far as running a virtual machine or even a real second computer.
Chriss
RE: Shortest method to know all fields updated by another user (in Table Buffering mode)
No, that was not the wrong query in my post.
I wrote, I was wondering when OLDVAL() and CURVAL() differed, why my GETFLDSTATE returned 1 instead of 2.
Then I realised, I had not changed the field value in the form and GETFLDSTATE would return 1 only.
Rajesh