What Is A Conflict?
In a multi-user database system, a conflict occurs whenever two or
more users are editing the same data at the same time. For example, the
following situation would create a conflict:
- User A edits a particular record but does not yet save changes.
- User B edits the same record.
- The users are now in conflict as one is editing non-current data.
The result of this action could be:
- User A saves changes.
- User B saves changes.
User A's changes have been lost because the values that were saved
by User B included the original data, before User A saved their
changes.
How Can You Avoid This Problem?
The solution to conflicts generally comes in the form of a locking
mechanism. This typically involves locking a record whenever a user
begins editing. In the above example, the action at number 2 would not
have been allowed.
Various database backends provide record locking, but more commonly
software developers will add a "LockUser" field to particular tables,
or maybe a "RecordLocks" table. These fields are manually set by the
application at the start of an edit and cleared after the edit is
applied or cancelled. While this approach is simple, there are a few
problems:
- A user's system crashes and hence leaves a lock even though the
connection is lost. To overcome this an administrator function for
unlock records is required.
- Concurrency restrictions. You can unnecessarily hinder
performance by making users wait for one another when this is not
necessary. This can have serious implications in busy systems,
particularly if table or page locking (as opposed to row locking)
schemes are used.
- There are extra queries on the SQL backend because each update
needs three queries, one to lock the record, one to do the update and
one to unlock the record.
How Can ASTA Providers Help?
ASTA Providers give another arguably better solution, where the
server "pushes" out changes that one user makes so all users see those
changes immediately. In the above example, action 4 would result in
User A's changes being visible to User B and hence User B's update
would not overwrite any data.
As of ASTA 2.5, providers are now capable of merging other users
changes without the need to write any code.
Each Asta Client Data Set (ACDS) registers with a provider on the
server to receive updates. In addition to this there are a few other
options listed under the ProviderBroadcast property of each ACDS:
- BroadCastAction
- baNone. Ignore all broadcasts.
- baCache. Cache all broadcasts for applying later. This is
commonly used as a Mutex to stop broadcasts being applied
while another action is in progress.
- baAuto (Default). Automatically apply the broadcast,
including merging in changes. This setting will apply any
outstanding cached broadcasts when set.
- CacheBroadCastsWhenEdit
A problem arises when a broadcast is received and the actual ACDS is
in edit or insert mode. The problem is the broadcast cannot be applied
because moving to another row in the ACDS (the row that the broadcast
relates to) would post the current changes and move the current record
- a very confusing situation for the user.
To overcome this problem, set CacheBroadCastsWhenEdit to be true
(Default) and the ACDS will automatically set BroadCastAction to be
baCache at the start of the edit, and baAuto after the edit (remember
setting it back to baAuto will apply any outstanding broadcasts).
- MergeEditRowBroadcasts
This property determines whether to merge broadcasts that relate to
rows that have been edited but not saved and is the basis for conflict
management. If this property is true (Default), all changes to rows
that have been edited (either currently being edited or in the pending
updates list when UpdateMode is umCached) will be merged in rather than
simply copying each broadcast field value. If false, broadcasts will
just be applied as normal by copying each field from the broadcast to
the ACDS.
The difference between merging and simply applying the broadcast is:
- Merge - User A changes "CustomerName", User B changes
"PhoneNumber" and both are not saved. When User A saves the changes,
only the "CustomerName" is updated on User B's screen because User B
had not changed this - if User B had changed the "CustomerName" field,
the change would be ignored and User B's value would be unaltered. When
merging in changes, the rule is simple - "If User B hasn't changed a
field, merge in that field's value from the broadcast. If User B has
changed the field, leave the changes intact".
- Normal Simple Apply - In the above example, when User A
saves the changes, both the "CustomerName" and "PhoneNumber" are
updated on User B's screen. This means there is no conflict because
User B sees the current data but is extremely annoying to User B
because their "PhoneNumber" changes are lost.
- ProviderFilter
The filter entered here will be applied at the server to determine
if a particular broadcast should be sent to this client. Typically,
this feature is used when a number of clients all share the one
provider but are only interested in certain changes (eg only for the
customer they have opened, but there is only one customer provider on
the server). The format of this expression is that same as the Filter
property.
This is not related to conflict management as such but is mentioned
as a useful companion feature.
Customising How It All Works (Events)
Events are provided at various stages of the conflict management
process to allow customisation. Generally, you will probably not need
any of these events and will not have to write any code.
Should you need them, the main events are:
- ProviderBroadCastEditRow
This event will be fired for each row that is being merged. It
allows the software developer to make a custom screen of what is
changed and to ask the user which changes they would like to apply.
Parameters to this event are:
- DataSet D
- This is the merge, containing fields "Field" (the name of the
field), "OldValue", "NewValue", "EditChanged" (true if the user has
changed this field) and "Apply" (true for all fields that you want
ASTA to merge in).
The dataset has one row for each field in the ACDS that is changed
in the broadcast and by default "Apply" is set to true when
"EditChanged" is false. You can display this dataset via the normal
Delphi / BCB database controls although note that "Field", "OldValue"
and "NewValue" are BLOB field due to the completely variable nature and
length of their contents.
Example of Contents of D:
| Field |
OldValue |
NewValue |
EditChanged |
Apply |
| "CustomerName" |
"OldPerson" |
"NewPerson" |
false |
true |
| "CustomerName" |
"OldPerson" |
"NewPerson" |
true |
false |
Additionally, if you wish to handle the broadcast yourself, set the
Handled parameter to true and if you want to keep the merge dataset (D)
for some other purpose set DisposeOfDataSet to false.
- ProviderBroadCastDeleteEditRow
A special case applies when User B deletes a row currently being
edited by User A. This event will fire for each row that is being
deleted when there are pending updates for that same row.
NOTE: That at the point this event is fired, the row has physically
been deleted from the database. If User A wants to keep the record, you
can update the OldValuesDataSet and change the "AstaChange" field from
dtEdit to dtAppend but this should only be done by experienced users.
If done, this means that the changes will be kept by adding a new row
with the old data but and important note is that if there are any auto
increment type fields, these will get new numbers.
The default ASTA action if you don't do anything is to delete the
edited row from the ACDS. If you handle the deletion yourself, set
Handled to true. The dataset D that is passed in, has its current row
as the broadcast for the deleted row.
This event will fire immediately when the broadcast is received (if
BroadCastAction=baAuto) and will ignore the CacheBroadCastsWhenEdit
property.
Other events that may be useful, but not directly related to
conflict management are (refer to online help for complete
description):
- ProviderBroadCastBeforeApplyRow
This event is fired before each broadcast row is applied. A Handled
parameter is provided if you want to take specific action and not have
ASTA take its default action.
- ProviderBroadCastBeforeApplyRow
This event is fired after each broadcast row is applied, and can be
useful for updating non-databound controls. Note that this event is
only fired after the actual broadcast is applied, so if
BroadCastAction=baCache this will not fire until later.
- ProviderBroadCast
This is the standard provider broadcast event that has been present
in ASTA for some time. You can set this event to completely take
control of the broadcast functionality.
Summary
Remember, that if you don't want to do anything special, no code is
required. ASTA will happily apply the default logic with just a couple
of property settings.
To enable conflict management via providers:
- Register the ACDS for updates.
- BroadcastAction=baAuto (Default)
- CacheBroadcastsWhenEdit=true (Default)
- MergeEditRowBroadCasts=true (Default)
The rest is done for you!
Conflict management can be a complicated issue, take time to
consider the theory behind what is being internally done by ASTA. This
provider "push" technology is just one option to handle conflicts -
record locking or other schemes are still worth considering and can be
better in some situations.
You should discuss record locking issues with your clients together
with associated costs. It is not uncommon to be formally told by a
client to ignore conflicts and hence not incur extra costs (the extra
costs can be significant when development and testing is taken into
consideration). This is of course decided on a project by project
basis.
|