Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Shortest method to know all fields updated by another user (in Table Buffering mode) 3

Status
Not open for further replies.

Rajesh Karunakaran

Programmer
Sep 29, 2016
549
MU
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

 
That's true, Rajesh. GETFLDSTATE() is still the basis of your search for conflicts.

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
 
Just to point out which is what:

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
 
Chriss,

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
 
To summarise:

- 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:
FOR lnI = 1 TO FCOUNT()
  lcFieldName = FIELD(lnI)
  IF OLDVAL(lcFieldName) <> CURVAL(lcFieldName)
    * Another has updated the value; take appropriate action
    ...
    ...
  ENDIF
ENDFOR

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
 
Mike,

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


 
Rajesh said:
[highlight #FCE94F]1.[/highlight] If <controlObject>.Value <> OLDVAL() means current user has modified it.
[highlight #FCE94F]2.[/highlight] If CURVAL() <> OLDVAL() means another user has modified it (maybe current user also modified it but another user's change is obvious)
Correct?
1. No, control.value could not be stored into the buffer, if it still has focus, and whether your user changed something is indicated by GETFLDSATE. Only GETFLDSTATE needs to be checked.
2. Yes.

Rajesh said:
If the current user HAS NOT modified it:
- if there is a change by another user, I will take that value for final saving.
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?

Rajesh said:
If ONLY the current user HAS modified it:
- I will take that value for final saving
This needs no consideration, TABLEUPDATE does save that.

If BOTH current user AND another user HAVE CHANGED it:
- I will ask the current user which value to save
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
 
A behind the scenes of how SQL by TABLEUPDATE looks like might enlighten you:

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:
UPDATE table SET firstname='Rajesh' WHERE id=10534 AND firstname='Rajeesh'
That's assuming the update to reidentify the original record by "key fields and modified fields". A conflict would only arise if another user also changed firstname. This update completely ignores lastname, both in the SET list and in the WHERE clause conflict check. Because it's not a modified field. That's the topic of "WhereType". But no matter if you only use key fields, the list of SETs done by TABLEUPDATE will aways only contain modified fields.

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
 
current user may want to override other user's change and force his/her value.

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
 
Mike Lewis said:
- 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.

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
 
Chriss and Mike,

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
 
So, basically, before saving, I need to check all those fields which are supposed to be edited by other users also for changes made by others. If I find something, then I need to check if the current user has changed any of them. If yes, I will need to ask the current user if he/she wants to force his/her value (in one or more fields) or go with value from others. As Mike said, the decision on this is purely a business rule matter.

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


 
Rajesh,

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.

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:
[tt]
REPLACE TheField WITH CURVAL("TheField")[/tt]

However, I think Chris disagrees with that. I'll need to real Chris's post more carefully.
If I find something, then I need to check if the current user has changed any of them. If yes, I will need to ask the current user if he/she wants to force his/her value (in one or more fields) or go with value from others.

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
 
Rajesh said:
So, basically, before saving, I need to check all those fields which are supposed to be edited by other users also for changes made by others.
No, you overcomplicate it. You can only be influenced by changes other users already saved. Their buffers are not in your access, and their saving of their buffer happens in their save routine, that's not your business.

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.

Rajesh said:
I will take values from other users, update my buffers and then go for 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
 
Chriss,

Chriss said:
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

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




 
Hi Rajesh

This code snippet from VFP HG7 might help you understand what GetFldState does and what it does NOT do


from VFP Hacker's Guide 7 said:
* You can prevent an update from failing by checking for
* conflicts ahead of time. Loop through all the fields and check
* whether other users have changed any that you changed.
* This example assumes you're dealing only with existing records
* and handles only a single record. Wrap it with a loop
* involving GetNextModified() to handle multiple records.

FOR nCnt = 1 TO FCOUNT()
* Did you change this field?
IF GetFldState(nCnt)<>1
* Did anyone else change it?
IF CurVal(FIELD(nCnt))<>OldVal(FIELD(nCnt))
* Conflict. You changed it and so did someone else.
* Ask the user what to do.
nNowWhat = MessageBox("Someone else changed "+ ;
FIELD(nCnt) + ", too. Save anyway?",
MB_YESNO+MB_ICONEXCLAMATION, ;
"Save Conflict")
IF nNowWhat = IDNO
* Grab the other user's value.
REPLACE (FIELD(nCnt)) WITH CurVal(FIELD(nCnt))
ENDIF
ENDIF
ENDIF
ENDFOR

[highlight #FCE94F]* Now you're ready to actually update the thing.
[/highlight]

hth

MarK
 
I wrote a little demo.

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...'
[highlight #FCE94F]   * Replace Lastname With Curval('LASTNAME')
   * SetFldState('LASTNAME',1)
   TableRevert(.F.)[/highlight]
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:
Local llConflict, lcStates, lnState
llConflict = .F.
lcStates = GetFldState(-1)
For lnState = 2 to Len(lcStates)
    If Val(Substr(lcStates,lnState,1))>1
       If Not OldVal(Field(lnState-1))==CurVal(Field(lnState-1))
          llConflict = .T.
       Endif
    EndIf
EndFor

Return llConflict
This checks conflicts of the current record changes with changes of other users. For understanding the code the most important thing to know is that GetFldSate(-1) returns all field states as a string of digits that each can be between 1 and 4, the first digit is not about field11, though, but about DELETED().

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
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top