Data-aware controls are "empty" after closing, reopening datasets and TDatabase - database

I recently updated an old Delphi project to separate the creation of its data module and opening of that module's TDatabase, TTable and TClientDataSet components from the creation and showing of the app's form. Now, the app can toggle on and off the data components of the app. This new capacity isn't critical of course, but is "nice to have." My tool chain is RS v21 (the marketing version 10.4.1).
Yet there's a problem: after closing and reopening the data components from the UI, data aware controls show no data. Tests show that the underlying tables are open, can be navigated, and field values can be retrieved programmatically.
I've also established that resetting a data aware component's DataSource restores its display of underlying field data.
NB: data aware controls' DataSource are set at design time and, at run time after closing and reopening data tables, are not nil.
I conclude that resetting DataSource has a side effect -- probably tickling the DataLink within.
You can imagine I would like a means to obviate the need to reset a whole bunch of DataSources. The easy hopes that TDataSet methods like Refresh all fail. ;-)
A sketch of the coding inside the app's form looks like this -- to open:
dm:=Tdm.Create(nil);
dm.OpenDatabase;
dm.OpenDataTables;
And closing is the reverse:
dm.CloseDataTables;
dm.CloseDatabase;
dm.Free;
dm:=nil;
In the database Tdm: In design, the datasets are not active, and the database is not connected. Tdm.OpenDatabase envokes Open on the TDatabase; Tdm.OpenDataTables runs through the needed tables using their Open method. Tdm.CloseDatabase and Tdm.CloseDataTables are symmetrical.
Thank you for any insights.

Related

Keeping repository synced with multiple clients

I have a WPF application that uses entity framework. I am going to be implementing a repository pattern to make interactions with EF simple and more testable. Multiple clients can use this application and connect to the same database and do CRUD operations. I am trying to think of a way to synchronize clients repositories when one makes a change to the database. Could anyone give me some direction on how one would solve this type of issue, and some possible patterns that would be beneficial for this type of problem?
I would be very open to any information/books on how to keep clients synchronized, and even be alerted of things other clients are doing(The only thing I could think of was having a server process running that passes messages around). Thank you
The easiest way by far to keep every client UI up to date is just to simply refresh the data every so often. If it's really that important, you can set a DispatcherTimer to tick every minute when you can get the latest data that is being displayed.
Clearly, I'm not suggesting that you refresh an item that is being edited, but if you get the fresh data, you can certainly compare collections with what's being displayed currently. Rather than just replacing the old collection items with the new, you can be more user friendly and just add the new ones, remove the deleted ones and update the newer ones.
You could even detect whether an item being currently edited has been saved by another user since the current user opened it and alert them to the fact. So rather than concentrating on some system to track all data changes, you should put your effort into being able to detect changes between two sets of data and then seamlessly integrating it into the current UI state.
UPDATE >>>
There is absolutely no benefit from holding a complete set of data in your application (or repository). In fact, you may well find that it adds detrimental effects, due to the extra RAM requirements. If you are polling data every few minutes, then it will always be up to date anyway.
So rather than asking for all of the data all of the time, just ask for what the user wants to see (dependant on which view they are currently in) and update it every now and then. I do this by simply fetching the same data that the view requires when it is first opened. I wrote some methods that compare every property of every item with their older counterparts in the UI and switch old for new.
Think of the Equals method... You could do something like this:
public override bool Equals(Release otherRelease)
{
return base.Equals(otherRelease) && Title == otherRelease.Title &&
Artist.Equals(otherRelease.Artist) && Artists.Equals(otherRelease.Artists);
}
(Don't actually use the Equals method though, or you'll run into problems later). And then something like this:
if (!oldRelease.Equals(newRelease)) oldRelease.UpdatePropertyValues(newRelease);
And/Or this:
if (!oldReleases.Contains(newRelease) oldReleases.Add(newRelease);
I'm guessing that you get the picture now.

Delphi XE3: DBLookupCombo Dropdown side effect

We are porting a D6 application to XE3.
In D6 I inherited a complex code which used shared datasets and datasources everywhere.
This worked well in D6.
After we could run the XE3 version, we experienced that lookup combo boxes changed.
On dropdown they reset the other dropdown's keyvalues (everywhere in the program)!
If two dropdowns use on dataset, and if I click on the first to down it, and select, on down the second keyvalue changed to NULL; and reverse - if I click on the second, the first's keyvalue change to NULL...
This is global in this program, so I need to find fast solution.
May somebody have any information about this "bug" (or "feature"? :-) ), or have a solution in his/her hand?
Thanks for any answer!
This is intentional. Take a look at the implemention of TCustomDBLookupComboBox.ListLinkDataChanged; in Vcl.DBCtrls. You will find the comment:
{ Fix for Defect# 204311, Non-bound control should clear when list datasource changes }
Solution: put your datasets on a data module. Instantiate that for every form, so every form works with a separate instance of the dataset. Make sure you set the name of the data module to an empty string after instantiation, or the Delphi streaming system will still use the first correctly named instance when hooking up the form's datasources with the datasets.
When the data module(s) are in the form's uses clause (interface or implementation doesn't matter) the IDE will still offer you their components through the Object Inspector.
You will want to put the database connection on a different data module that you only instantiate once (possibly automatically).

handling non-standard ext js grid-server comunication

The ext js (v4) grid I have receives collections of json records at any given time, where each record has a tag on it that tells the grid the operation to perform alongside other attributes. For example, in an incoming collection a grid might encounter the following (with the id being the key):
{"records":
[{"id":"101", "name":"I'm new", "op":"create"},
{"id":"102", "name":"I'm old", "op":"delete"},
{"id":"103", "name":"I'm different", "op":"update"}]
}
I'm trying to write the code to tell the grid or store to perform these various operations on the models, but to not have that operation then post back to the server (after all, that's where the directive came from). Simply put the client grid should represent what's on the server, where the objects can be popping in and out of existence.
I've looked into the readers and writers for proxies, or inheriting from the json proxy itself and modifying the read/write behavior, but it seems as though I'd still need to call the destroy/create/update commands on the models themselves and then somehow short-circuit the model behavior so they don't send that crud operation back to the server. None of these options feel quite right, however.
Is there a ext-js component I should be using in this case instead of the proxy/read/write objects?
You are over thinking this.
You don't need to send operations back to the grid. If your server sends a new set of data with a missing record (deleted) it will not show up in the grid. If you send a changed data set to the grid (write) those changes will just show up. And if you add record on the server side and send the set to the grid - a new record will show.
Basically if your server side drives all of the changes then you don't need the writer config and just have the read only grid.

How to update a TClientDataSet with changes made to the DB without it knowing?

I have a simple TClientDataSet component that I use to populate some data-aware components. However, if I insert data into my database using this dataset, I can't seem to find a proper way to sync it back into my TClientDataSet component.
How do I achieve this?
The TClientDataSet component has a Refresh method that does exactly that. Took some time for me to find this out in the docs, though. :)
From the docs:
From DB.pas
procedure Refresh;
Re-fetches data from the database to update a dataset's view of data.
Call Refresh to ensure that an application has the latest data from a database. For example, when an application turns off filtering for a dataset, it should immediately call Refresh to display all records in the dataset, not just those that used to meet the filter condition.
Note: The Refresh method does not work for all TDataSet descendants. In particular, TQuery components do not support the Refresh method if the query is not "live". To refresh a static TQuery, close and reopen the dataset.
TDataSet generates a BeforeRefresh event before refreshing the records and an AfterRefresh event afterwards.
Note: Most datasets try to maintain the current record position when you call refresh. However, this is not always possible. For example, the current record may have been deleted from the server by another user. Unidirectional datasets have no mechanism for locating the current record after a refresh, and always move back to the first record.
Warning: Unidirectional datasets refresh the data by closing and reopening the cursor. This can have unintended side effects if, for example, you have code in the BeforeClose, AfterClose, BeforeOpen, or AfterOpen event handlers.
Closing and opening the CDS didn't work for me. I'm using Delphi XE2, Update 3, with ADO tables connecting to an Access 2007 database. The only way I could refresh the data in my CDS was this:
procedure TForm1.PeopleRefreshButtonClick(Sender: TObject);
// this actually re-loads the data from the table!
begin
DM1.PeopleTable.Close;
DM1.PeopleTable.Open;
DM1.PeopleCDS.Refresh;
end;
A refresh button on the form closes, then opens the table. Then I refresh the ClientDataSet.
Basically, you must close the TClientDataset and then open it, loading the data from the database the same way you did originally. If the TClientDataset is connected to a TDataSetProvider, which is connected to a TDataset/TQuery descendant, all you have to do is close TClientDataset then open it.

Retrieving common data on different forms

Lets take an example of WinForms applcation and making invoice. On the Invoice form we retrieve a list of products, so the user will be ale to pick products for current invoice. Lets also consider that during this process user realizes that he needs to add a new product (or edit current) to ProductList before he can place it in invoice. So he opens a ProductForm where all the products are retreived (again).
It could also be in opposite order, that user first edits Products, and then without closing the Products Form, opens new Invoice. The principle is that data is two times loaded, and effectively its the same data.
What is the propper way to handle this scenario, so we can tell one form that data is already loaded, and to retrieve that data from memory? And when all the consumers (Forms) of the data are closed, then also the data should be released from memory? Or I am going in wrong direction, and there is a better way?
Thanks,
Goran
Definitelly go with data loaded "twice" or you will introduce much worse problems.
Sharing data means sharing ObjectContext. Even in WinForms application this is considered as bad approach. Check this article (it is about NHibernate but the description is valid for EF as well).
The problem is that ObjectContext is unit of work. If share context between two windows you can easily get into situation where you modify data in first window (without saving them!) and you continue in second window where you push save button but it will save data from both windows! You can't selectively save data only from one window when you share the context.
If the Controls that are using the data are all child controls of a shared Parent control, then you could just pass around the datacontext, so that they all shared the same datacontext.
However, the general use case with databases, which is what backs EF in most cases, is to read the data in each time that it is needed.
A solution to this if as you say you already have the item being used in one form is to just take a Refrence to that item into your new form.
So in the case Where you have an invoice which has a Product List and you want to add to the product list, you could pass the product list from the invoice to the opening product list.
There are some issues with this:
If another user changes the datasource while one has opened it (a.k.a. Concurrency)
Handling save don't save scenarios where they may have made a change in one area that they don't actually want added to the data.
However, unless it is a true performance issues, I would just load the data every time. You can simplify this a lot by using the repository pattern, so you can just call a single method to get a list of products or an invoice, or whatever part of data you need.

Resources