at the moment we are migrating the database component of our Delphi7 application from the BDE components to the AnyDAC Version 8.0.5 components.
The TTable (BDE) has the following behavior, before editing the record from another application instance (session):
The record is refreshed and changes are visible from other instances. The record will be refreshed in the method TBDEDataSet.InternalEdit.
The dataset is set into edit mode (DataSet.State = dsEdit)
Using the appropriate AnyDAC components (TADTable) the records does not reflect the changes done by other instances.
No special changes to TADConnection and TADTable are made.
Any help appreciated.
I cannot speak for BDE as I don't want to get in touch with it anymore, but what you've described I can read like:
Why does not AnyDAC refresh the tuple before editing starts?
If that is so, and correct me if I'm wrong, that would be quite against UX. Imagine, that you were a user of your own application and wanted to edit a certain tuple in a data grid view. You would click some edit button to enter editing mode, and the whole row would suddenly change in front of your eyes (or editor would be filled by different data than you've seen). Would you like this to happen?
If so, then I'm afraid you'll need to perform such refresh manually with AnyDAC (or FireDAC). The point here is that the engine either locks the tuple by transaction, or tracks the changes inside the internal storage whilst you're in the editing mode.
In neither case refreshes the tuple before editing starts (no matter which locking options you use). And I'm personally fine with this behavior as it could lead to what I've described above.
So how can I refresh the active tuple before editing starts then?
To refresh particular tuple to which the dataset cursor points before dataset editing starts you can call e.g. RefreshRecord from the BeforeEdit event, for example:
procedure TForm1.ADTable1BeforeEdit(DataSet: TDataSet);
begin
TADTable(DataSet).RefreshRecord;
end;
But then your database editing capability becomes a moving target (well, maybe it is already).
Related
the code i'm working on makes heavy usage of TFDMemTables, and clones of those tables using CloneCursor.
Sometimes, under specific conditions which I am unable to identify, the source table and its clone become out of sync: the data between them may be different, the record count as well.
Calling Refresh on the cloned table puts things back in order.
From my understanding, CloneCursor is used to address the same underlying memory where data is stored, meaning alterations to the underlying data from any of the two pointers should reflect on the other table, yet allow the user to have separate filter / record positioning per "view". so how can it possibly go out of sync?
I built a small simulator, where I can insert / delete / filter records in either the table or its clone, and observe the impact on the other one. Changes were reflected correctly.
Another downside of Refresh is that it slows the execution tremendously, if overused.
Has anyone faced similar issues or found explanations / documentation regarding this matter?
Edit:
to clarify what I mean by "out of sync", it means reading a value from the table using FieldByName will return X prior to Refresh, and Y post-refresh. I was not able to reproduce this behavior on the simulator mentioned above.
I'm developing a software that displays information in a DBGrid via a TSimpleDataSet (dbExpress components)
The software in question is used on 2 different computers by 2 different people.
They both view and edit the same information at different times.
I'm trying to figure out a way to automatically update the DBGrid (or rather, the DataSet, right?) on Computer B once Computer A makes a change to a row (edits something/whatever) and vice-versa.
Currently I've set up a TButton named Refresh that once clicked executes the following code:
procedure TForm2.actRefreshDataExecute(Sender: TObject);
begin
dbmodule.somenameDataSet.MergeChangeLog;
dbmodule.somenameDataSet.ApplyUpdates(-1);
dbmodule.somenameDataSet.Refresh;
dbmodule.somename1DataSet.MergeChangeLog;
dbmodule.somename1DataSet.ApplyUpdates(-1);
dbmodule.somename1DataSet.Refresh;
dbmodule.somename2DataSet.MergeChangeLog;
dbmodule.somename2DataSet.ApplyUpdates(-1);
dbmodule.somename2DataSet.Refresh;
dbmodule.somename3DataSet.MergeChangeLog;
dbmodule.somename3DataSet.ApplyUpdates(-1);
dbmodule.somename3DataSet.Refresh;
end;
This is fine and works as intended, once clicked.
I'd like an auto update feature for this, for example when Computer A edits information in a row, Computer B's DBGrid should update it's display accordingly, without the need to click the refresh button.
I figured I would use a TTimer and set it at a specific interval, on both software on both PC's.
My actual question is:
Is there a better way than a TTimer for this? If so, please elaborate.
Also, if the TTimer route is the way to go any further info you might find useful to state would be appreciated (pro's and con's and so on)
I'm using Rad Studio 10 Seattle and dbExpress components, the datasets connect to a MySQL database on my hosting where my website is.
Thanks!
Well, Ken White and Sertac Akyuz are certainly correct that using a server-originated notification to determine when to refresh your local dataset is preferable to continually re-reading all the data you are using from the server.
The problem AFAIK is that there is no Emba-supplied notification system which works with MySql. See this list of databases supported by FireDAC's Database Alerts:
http://docwiki.embarcadero.com/RADStudio/XE8/en/Database_Alerts_(FireDAC)
and note that it does not list MySql.
Luckily, I think there is a work-around which should be viable for a v. small system like yours currently is. As I understand it, you and your colleague's PCs are on a LAN and the MySql Server is outside your LAN and on the internet. In that situation, it doesn't need a round trip to the server for one of you to get a notification that the other has changed something in the database. Using an analogy akin to Ken's, you can, as it were, lean over the desk and say to your colleague "Hey, I've changed something, so you need to refresh your data."
A very low-tech way of implementing that would be to have somewhere on your LAN a resource that both of you can easily get at, which you can update when you make a change to the DB that means that the other of you should update your data from the server. One way to do that is to have a small, shared datafile with a number of records in it, one per server db table, which has some sort of timestamp or version-ID number which gets updated when you update the corresponding server table. Then, you can periodically check (poll) this datafile to see whether a given table has changed since you last checked; obviously, if it has, you then re-read the data you want from it from the server and update your local record of the info you read from the shared file.
You can update the shared file using handlers for the events of your Delphi client-side datasets.
There are a number of variations on this theme that I'm sure will be apparent to you; the implementational details really don't matter.
To update the shared file I'm talking about, you will need to lock it while writing to it. This answer:
How do I get the handle for locking a file in Delphi?
will show you how to do that.
Of course, the shared local resource doesn't have to be a data file. One alternative would be to use a Microsoft Message Queue service, which is sometimes used for this kind of thing, but has a steeper learning curve than a shared data file.
By the way, this kind of thing is far easier to do (at least on a small scale like you have) if you use 3-tier database access (e.g. using datasnap).
In a three tier system, only the middle tier (a Delphi datasnap server which you write, but it's not that hard) talks to the server, and the clients only talk to the middle tier. This makes it easy for the middle tier server to notify the other client(s) when one of them changes the db data.
The three-tier arrangement also helps minimise the security problems with accessing a database server via the internet, because you only need one secure connection to the server, not one per client. But that's straying a bit far from your immediate problem.
I hope all this is clear, if not, ask.
Just use a timer and make it refresh the dataset every 5 min. No big deal.
If the usage is not frequent then you can set it to fire every 10 or 15 min.
There is nothing wrong with the timer if it set on longer intervals.
Today's broadband connection's can easily handle the traffic so can Access.
If the table is not huge of course.
I'm trying to implement the pessimistic lock on my application and follow tutorials on Firedac explaining how to proceed. In my query I am informing Lock Mode lmPessimistic and Lock Point lpImmediate.
The lock works perfectly, not allowing me to access this record from another application instance. But it causes me a problem with transactions I do not know to solve, because never have explicitly worked with them.
Example:
I enter the screen using Edit;
Change the values;
I have to use a ApplyUpdates (0) CommitUpdates and Edit in the Query, because it is a master-detail;
Finally I record everything and close the screen.
Works perfectly, I refresh the record in my other open application and values are updated. But if I do the same test again, when I close the screen, the values are not updated in my second application. They are only updated when I close the first application.
I have found that what is causing it is the Query.Edit in the process. I think it's a problem with transactions, but did not put any type of component in the project, I know firedac is responsible for creating them for me. But I will have to set up anything else?
P.S.: I'm using CachedUpdates = True
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 8 years ago.
Improve this question
I have a software that uses a SQL Server express database. When the software runs local the data is loaded fast however when I run it remote there is always some delay populating grids etc..etc..
I'm looking to make a some kind of preloader or an alert box that appears with a bar indicating that data is being loaded into the software and prevent the user from clicking on the form.
Can you guys point me to a tutorial or if you can give just a general idea how to accomplish that ?
Forget optimisations like background threads and async dataset loading until you've got the basic workflow of your app correct. Generally, the thing to do with datasets is to open the minimum number necessary to permit the current user operation, and open others, e.g. needed for drilling down into a selected patient's details only as needed. In each case you open the dataset before the related form is shown; that way, the opportunity for the user to try working with only partially loaded data never arises.
So in a situation like this apparently is, where the user browses a collection of patients
in a Patients table, start out with a form containing a DBGrid connected to a dataset component
that delivers the Patient rows. Don't show the form until after you've opened the Patients table, in read-only mode. And don't open any other datasets yet.
Presumably there's a collection of patient detail tables that need to be opened to show the data of a given patient on one or more forms - I imagine there might be a top-level Patient's ID Details form,
and maybe a number of drill-down ones which can be invoked from it. Again, don't show this form(s) until the tables needed to supply the patient data are open. The easiest way to make the user aware that they should wait while something completes
is to surround the code involved with something like this:
Screen.Cursor := crSqlWait;
Screen.activeForm.Update; // refreshes the current form to ensure
// the cursor gets updated on-screen
// Open the patient details table(s) and create the related form(s) here
Screen.Cursor := crDefault;
// Now, show whichever is the principal patient detail form
Once the user has finished with a patient's details, close the form(s) that were opened to do it and close the related datasets.
Sql Server and Delphi are quite capable of populating a top-level DBGrid with outline info for several thousand patients with hardly any perceptible delay, as long as the data is all retrieved into one dataset (e.g. an AdoQuery) using one SQL SELECT statement. Don't take my word for it, try it with your own data. If it seems to slow, you're doing something wrong.
The key is not to attempt to do more than you need to at the time. As I've explained, only retrieve patient-specific data once the user has selected a top-level patient record to work on. Until the app knows which patient the user is working one, it's pointless trying to retrieve patient-specific data of the type you mentioned in comments and would only slow down the app and generate needless network traffic.
IN my application I use DBAware components exclusively (except a few places).
I have a scenario in which I create a Master dataset (e.g. customer), detail dataset (e.g. Orders), subdetail dataset (e.g. order items). TYpically I allow users to make changes (the dataset are in Browse mode) and then I post. Simple.
Anyway on editing the subdataset I want to add a kind of simple undo feature: one opens a form to edit the dataset (that is with db componets, so changes to the form will change the dataset), if the user Cancels the operation I would like to restore the dataset as it was before opening the form.
Now for implementing this I can think of makeing a copy of the dataset in a TClientDataSet or similar component, but are there other techiniques? Like is it possible with Delphi to create in an easy way a "snapshot" of the data. With pseudocode:
MySubDetailDataSet.SaveSnapShot;
SubDetailForm.ShowModal;
if ModalResult = mrCancel then MySubDetailDataSet.RestoreSnapShot;
Is something like that possible "off the shelf" with Delphi components?
By the way I use SDAC from DevArt components, so if you know a technique that is available only with those components and not with Delphi standard ones it is welcome!
In a client dataset changes are stored in a delta - you can call CancelUpdates to clear the delta and revert to the original dataset. There are other more granular approaches. See "Undoing changes" in the help.
If you're using a RDBMS and you're properly inside a transaction, you can rollback the transaction. Some databases offer savepoints to rollback to a given savepoint instead of rolling back a whole transaction, but that's database specific. Transactions usually are per session, not per single table or query. You have to be sure only the changes you may need to roll back are performed in a given transaction.
Client dataset may be a "lighter" approach because they manage data client-side only and don't require database resources. While you're in a transaction inside the database, some resources are needed to keep track of it and changed data. Transaction should be as long as required, but not longer.
Be also aware that transactions may imply some locks. Lock management can be very different from database to database, and some may escalate locks, blocking more users than needed. Always test with a sufficient number of concurrent users to ensure transactions are used properly.
In AnyDAC you can do:
var
iPrevSP: Integer;
...
iPrevSP := MySubDetailDataSet.SavePoint;
SubDetailForm.ShowModal;
if ModalResult = mrCancel then
MySubDetailDataSet.SavePoint := iPrevSP;
The similar technique is accessible with TClientDataSet, kbmMemTable. Not the answer probably, as you are using DevArt product.
With DevArt I managed copying data to a TVitualTable (a DevArt Version of a TCLientDataSet), anyway SavePoint feature as easy as in AnyDAC is not there.
You can use TClientDataset and load it from file or stream and save the original data inside, and every time you want to rollback, reload it from original data.