Is it possible to take a snapshot of a dataset? - delphi

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.

Related

Avoid too long active transaction with IBX components

My question is very simple, what is the best practice to avoid to have too long active transaction with an application that use many component TIBDataSet? I would avoid to have very old OAT and than have very bad performance
My application have more dataset that must be always opened (until the application is running). I would avoid to close and reopen the transaction because I will be reopen all dataset.
I must be replace this component?
And if yes, what is the best choice?
ClientDataSet with DataSetProvider or switch to IBO component (also If I wouldn't install other component on my IDE)
Read-only transactions don't affect performance of FB server. In our project we use single projectwide read-only always open transaction for data fetching and multiple short living transactions for data modification.
We use modified IBX components where second separate transaction for data reading was added.

Auto refresh a TDataSet / DBGrid

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.

Interbase transaction monitoring

I have a very strange problem with transactions in Interbase 7.5 which seem to be stuck.
I can track the problem with IBConsole -> right click DB -> Performance Monitor -> Transactions
Usually this list should show only a few active transaction. But I get several hundred active transactions when I start my application (a web module for an apache webserver using Delphi 7 Interbase components, e.g. IBQuery, IBTransaction, ...)
Transaction type is always listed as snapshot, if this is of relevance.
I have already triple checked all sql statements and cannot find anything that should produce such problems...
Is there any way get the sql statements of a specific transaction?
Any other suggestion how to find such a problem would be very welcome.
Is there any way get the sql statements of a specific transaction?
Yes, you can SELECT from TMP$STATEMENTS WHERE TRANSACTION_ID = .... That's from memory, but should get you started.
In IB Performance Monitor, you can locate the transaction from the statements tab, using the button on the toolbar. Can't remember if you can go the other way in that app. It's been a long time since I wrote it!
Active IBX data-sets require an active transaction all the time. If you don't have active data-sets just don't forget to commit all the active transactions.
If you have active data-sets, you can configure all your components to use the same TIbTransaction object, and you can also configure the unique TIbTransaction to commit or rollback after a idle time-out period via the IdleTimer and DefaultAction properties.
Terminating the transaction (by manually or automatically committing or rolling back) will close all the linked datasets (TIBQuery, TIBTable and the like).
You may be tempted to use the CommitRetaining or RollbackRetaining methods to terminate the transaction without closing the related data-sets, but this may affect the performance of the server, and my advise is to always avoid using it.
If you want to improve your application, you should consider changing your database connection layer or introducing a in-memory capable dataset over IBX, for example, Delphi's TClientDataSet, which allows you to retrieve data and retain it in memory while closing all the underlying datasets (and transactions), while allowing you to use the traditional Insert/Append/Edit/Delete methods to modify the data and then apply that changes to the database in a new short-time transaction.

Is it possible to have a detached TpFIBDataset for FIBPlus?

I remember that when I was working with ADO for Delphi (dbGo) there was a possibility of creating a detached dataset. The idea was that I could read all the data which I wanted from database and then set the connection property to nil. That caused TADOQuery to work as a memory table. I could then use and pass TADOQueryas a TDataSet parameter to my other methods without worrying that I am keeping unnecessary connection or transaction opened.
I would like to have the same functionality when using FIBPlus library. Currently I need to copy data from TpFiBDataset to other structure and then close the data set. Otherwise to access the rows of dataset, transaction must stay opened, even if I have all the data fetched.
I could not achieve detached dataset functionality on my own, is this possible?
No. TpFIBDataSet could not work as a standalone dataset. You should use TpFIBClientDataSet (if you want to apply later updates to db) or any TInMemoryDataSet (just for local reading).

Suggestions for caching a dataset

I'd like to perform the following:
1) Open a dataset (using TMSQuery, SDAC DevArt component)
2) caching the content to disk (imagine a list of cutsomers)
3) the next time I need to Open the dataset I will first populate it with cached data, then I will just Refresh it by calling TMSQuery.RefreshQuick method.
In this way I plan to obtain a substantial improvement in speed, because I don't need to retrieve records that I already retrieved in previous application runs.
How can I obtain this caching? I have many datamodules with TMSQuery, so somehow I would like to have a global routine that checks that everytime I try to Open a TMSQuery, if that query is somehow tagged i will try to restore from cache, call RefreshQuick, in case this fails I will call Open.
Can you please suggest?
(I use Delphi 2009 and SDAC 4.80)
you can use the TClientDataSet and TDataSetProvider components for this, connecting the components in this way.
TMSQuery->TDataSetProvider->TClientDataSet
The TClientDataSet is a very good alternative to persist and retrieve data from an disk.
see these links for more info about the ClientDataset
Using the MIDAS ClientDataset as a replacement for cached updates
A Guide to Using the TClientDataSet in Delphi Database Applications
Effective ClientDataSets and the BriefCase Model
You can do 2 things:
Make descendant of the TMSQuery component and override the Open function
(you search all you datamodule .dfm and .pas files with TMSQuery and replace with TCachedTMSQuery)
Detour/hook the TMSQuery.Open (runtime patching)

Resources