Apart from the new name DataSnap replacing DataSnap, C++Builder 6 introduced some real new enhancements as well, such as local connections and connection brokers . The former was introduced because the role of the TClientDataSet component was significantly enlarged in C++Builder 6. We now have the TClientDataSet component in the Professional version of C++Builder, instead of only the Enterprise version. And, we can now have local DataSetConnections instead of only remote DataSetConnections, as we'll see in this section.
Specifically, I want to focus on two new components with icons that (almost) look alike: the TConnectionBroker and the TLocalConnection component (I won't cover the TSharedConnection ). The final section of this chapter will focus exclusively on DataSnap and SOAP (using SOAP Data Modules and the TSOAPConnection component).
Let's start with the TLocalConnection component. This component can be handy for people who want to start building distributed applications, but are not ready to implement the server, yet. As you might know, it's not really required to put the TDataSetProvider and TClientDataSet in different tiers of a multitier application. They can actually reside in the same single- tier application (previously leaving the RemoteServer property of the TDataSetProvider component a bit useless). However, this single-tier application might be a convenient way to prepare your multitier application. You are limited because you cannot implement actual access to the IAppServer interface (the interface that's normally provided by the remote data module to the client application, and through which the TDataSetProvider exports its dataset property to the local TClientDataSet ). To take the single-tier simulation one step further, Borland has now provided us with the TLocalConnection component. This is, in fact, almost a dummy component with one great feature: It implements the IAppServer interface and can be used to assign any TDataSetProvider to (as if the data was retrieved from the Connection component by some communication means). The benefit should be obvious: The client part of the application (still single-tier at this time) can now implement and test the use of the IAppServer interface as implemented by the TLocalConnection component. This means that you can postpone the actual split of your application in two physical tiers until you're really ready, which might help to keep things under control (and maintainable as well as manageable).
I understand if that doesn't really make much sense to you ( especially if you've seldom built prior multitier applications using DataSnap). So, let's just build a practical example where I'll show you exactly where and when the TLocalConnection component comes into play, and how it changes the rules of the game.
We could have used any database access layer in C++Builder 6 for this example, but because dbExpress can already make use of embedded TClientDataSet components (in the TSQLClientDataSet component ”see Chapter 12), it seems more appropriate to use the Borland Database Engine (BDE) as our start point. That way we can also keep the illusion of a legacy application that needs to be upgraded to a multitier architecture.
To build our "legacy" application, start C++Builder 6 and create a new application using File, New ”Application. Save the project in Legacy.bpr and the main form in LegacyMainForm.cpp . Click File, New ”Data Module to add a new data module, and save that in DataMod.cpp . Note that the main form will only be used to show the contents of the datasets on the data module. I assume you can manage to build that part yourself (so won't cover it in this chapter).
Now, we'll move on to the data module. Because we're building a BDE application, we need to drop a TDataBase component from the BDE tab of the Component Palette, set its DatabaseName property to BDE, and its AliasName property to DBDEMOS . Add a TSession component and set its AutoSessionName property to true . Now drop two TTable components on the data module, assign their DatabaseName properties to BDE (the one we've just made), and rename them to tblCustomer and tblOrders , respectively. Now, set the TableName property of tblCustomer to customer.db , and the TableName property of tblOrders to the orders.db table.
We're now ready to define a master-detail relationship between tblCustomer and tblOrders , using a TDataSource component from the Data Access tab. Set its name property to dsCustomerOrders , and its DataSet property to tblCustomer . Now, click tblOrders , and set its MasterSource property to the dsCustomerOrders DataSource. Click the ellipsis for the MasterFields property to start the Field Link Designer, use the CustNo index, and select the CustNo field for both tables to be the field for the master-detail link. Click OK to create the master-detail relationship. See Figure 21.2 for the data module, so far, that we can use in a normal (legacy) BDE application right away.
Now, although the BDE application we've just created works just fine, there might come a day when you need to turn it into a distributed application ”or at least make the preparations to do so. For example, because the database must be placed on its own ”more secure ”machine, or because you will also be required to accept browser-based clients in the near future (apart from Windows GUI clients ).
A distributed application is based on a TDataSetProvider component on a Remote Data Module at the server side, and a TxxxConnection and TClientDataSet at the client side (where TxxxConnection can be any of the connection component we've seen). To prepare for that architecture, we should now drop a TDataSetProvider component from the Data Access tab onto the data module. Set its Name to dspCustomerOrders (we will use it to provide both the Customer master and the Orders detail records), and set its DataSet property to tblCustomer . The TDataSetProvider will now export the Customer master table with the Orders detail records embedded as a so-called nested dataset.
However, because the TDataSetProvider is used on a normal data module, and not a regular data module, there is no way it will be actually exported to the outside world. Fortunately, we can still use it (and have used it in the past), as a local TDataSetProvider . The trick is that a local TClientDataSet will be able to connect to a local TDataSetProvider as long as they both share the same owner. In other words, as long as they are both placed on the same data module or form, they can talk to each other!
So, drop a TClientDataSet component on the data module, set its Name to cdsCustomer and assign its ProviderName property to dspCustomerOrders . This method also worked with C++Builder 5. However, there is a better way that also helps if you want the TClientDataSet to use more than just the TDataSetProviders that are available on this single-data module. It's also better if you want to make specific IAppServer method calls to prepare yourself for a move to a real distributed architecture (where the TDataSetProvider will end up in the server tier, and the TClientDataSet component will have to use a TxxxConnection component to talk to the server and get a list of TDataSetProviders ). The better way consists of dropping the TLocalConnection component (the topic of this section). Using a TLocalConnection component, a TClientDataSet component can have access to all available (local) TDataSetProvider components in your entire application ”not just the ones that exist in the same data module or form.
To show the "better" way, drop a TLocalConnection component (from the DataSnap tab) on the data module. This component only has two properties: Name and Tag , so there's nothing you can customize or configure. Note that it's a global application component, and you only need one of them to service your entire application.
After the TLocalConnection component is in place, you can go back to your cdsCustomer TClientDataSet component, and assign its RemoteServer property to LocalConnection1 ; the local connection component. This time, when you open the drop-down combo box for the ProviderName property of cdsCustomer , you will get a list of all DataSourceProviders in your entire application (there is still only one, but believe me, you'll get them all at this time). Select dspCustomerOrders to connect to this particular TDataSetProvider . Note that we now connect to the TDataSetProvider through the IAppServer interface (which is implemented by the TLocalConnection component). The difference is that it will be much easier to migrate to a real RemoteServer using a TxxxConnection component later ”because we will see in the remainder of this section.
To finish the data module example, you need to drop a TDataSource component, set its Name property to dsCustomer , and connect it to the cdsCustomer ClientDataSet . If you want to explicitly use the Orders detail records as well, you must right-click the cdsCustomer ClientDataSet , start the Fields Editor, right-click in the Fields Editor and select Add All Fields. The list of fields will include a field named tblOrders . This is the nested dataset (of type DataSetField ) that we created earlier in this example. We can use this explicit (also called persistent) DataSetField to feed another TClientDataSet component. So, drop a second TClientDataSet on the data module, set its Name property to cdsOrders and this time you only have to open the drop-down combo box for the DataField property and select the (only) value cdsCustomertblOrders . A TDataSource component named dsOrders connected to the cdsOrders finishes the preparation to this point (see Figure 21.3).
You can now use the dsCustomer and dsOrders DataSources to connect to data-aware controls (on the main form).
Of course, working with ClientDataSets means caching your data, so there's one more change that you have to make to the data module. You must make sure to call the ApplyUpdates method of the cdsCustomer ClientDataSet to apply all changes (edits, inserts , deletes) from the ClientDataSet back (through the TLocalConnection component) to the DataSetProvider and the actual database tables. Note that you don't need to do this for the cdsOrders ClientDataSet because that one isn't even connected to a RemoteServer or ProviderName ; it's simply connected to a DataSetField .
For the cdsCustomer , there are a few ways to make sure the ApplyUpdates method is called. First of all, you can put a button on your form and ask your end user to click the button to explicitly call ApplyUpdates . Of course, this means that you must know for sure that the end user will in fact click the button. This is something that you can't debug or fix; you just have to trust it. When you do this, at least make sure to ask the user to save all changes when she tries to close the main form. The ClientDataSet maintains a ChangeCount property, which holds the number of changes. If this property has a value greater than zero, you know for sure that some changes have been made (and have not been applied, yet). I always implement this as follows in the OnClose event of the main form:
#include "DataMod.h" void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { if (DataModule1->cdsCustomer->ChangeCount > 0) { if (MessageDlg("Save all changes?", mtConfirmation, mbYesNoCancel, 0) == mrYes) DataModule1->cdsCustomer->ApplyUpdates(-1); } }
At least then we know for sure that the user can never forget to save the changes and updates (she can refuse them, but not by accident ).
As a final remark, note that the ApplyUpdates might fail when another (concurrent) user has also changed the same record in the remote database. Because we're currently only preparing to upgrade our legacy BDE application to a distributed multitier architecture, I won't discuss that topic. For more information about the Reconcile Error Dialog , see the previous Chapter 20, "Distributed Applications with DataSnap."
At this time, we have a data module that is still a single tier, but has all aspects of a multitier solution inside. The application can now make use of the dsCustomer and dsOrders DataSources , as well as the cdsCustomer and cdsOrders ClientDataSets , and even the IAppServer interface exposed by the RemoteServer that the cdsCustomer ClientDataSet is connected to. The fact that ”in this version of the data module ”the RemoteServer points to a TLocalConnection component doesn't matter; we can still use the IAppServer interface as if we're already working in a multitier distributed architecture. This way, without actually having multiple physical tiers, you can already prepare and set up the entire presentation layer of your application ”the GUI part, for example, or a TXMLBroker component, which also has to connect to a RemoteServer to get to the DataSetProvider .
We'll continue with the example application(s) by continuing our potential upgrade to multitier path with the new TConnectionBroker component.
The TConnectionBroker component is another component that can come in handy when you want to configure or maintain your DataSnap application. Like all actual TxxxConnection components, the TConnectionBroker component is only useful in DataSnap client applications, and can be used as an additional layer between TClientDataSets and the actual TxxxConnection components. As such a layer, it can be used to quickly switch between different TxxxConnection components. In case you wonder why this would be useful for a single or even a few TClientDataSet components (which can easily be moved from one TxxxConnection component to another), just consider the situation where you have one hundred TClientDataSet components.
Believe me, this will turn out to be a nightmare if you want to switch them over from one TxxxConnection component to another. Just imagine for a moment that you accidentally forget to switch one ClientDataSet over! Your application will still compile, and it will even run, but then the problems start (especially if you've deployed it to your clients already). Unless the ClientDataSets are all connected to the TConnectionBroker component, which can then be used to switch from, say a TLocalConnection to a TDCOMConnection or a TSocketConnection component with the ease of a single-mouse click.
To show this particular feature in action, let's continue with the previous example application, taking it one step further by adding an actual TDCOMConnection component, and then using the TConnectionBroker to switch between the TLocalConnection and the TDCOMConnection . Of course, this requires a DataSnap server as well, so let's start with the server now, and then come back to modify our data module (which is already getting pretty crowded for a small example).
If we take a look at the data module that we have so far, it should be clear that we can actually split it in two separate parts (also called tiers): a server part and a client part. The separation should take place right between the TLocalConnection and the TDataSetProvider component. The server side consists of the TDataBase , TSession , two TTables , a TdataSource , and the TDataSetProvider , and the client side contains the rest. Because we don't want to break the legacy application, we won't actually remove the server-side components from the remote data module, but simply copy them. But first we need to create the DataSnap Server using File, New ”Application. Save the project in DataSnapServer.bpr and the main form in MainForm.cpp . This time we need to add a remote data module. Click File, New ”Other, go to Multitier and select the Remote Data Module icon. The Remote Data Module will be the remote object that implements the IAppServer interface on the server side. The Remote Data Module Wizard asks for the CoClass name of the Remote Data Module (the name of your class as well as interface, which will be derived from IAppServer ). Let's take RemoteDataMod42 (see Figure 21.4). Leave the Threading Model value at its default ”see the previous chapter for more information about this setting.
When you click OK, the new Remote Data Module has been created. If you start the Type Library Editor from the Views menu, you'll notice that it contains the definition of the IRemoteDataMod42 interface, and that the parent interface of IRemoteDataMod42 is set to IAppServer . If you want, you can now add your custom properties and methods to IRemoteDataMod42 .
Close the Type Library Editor (we'll get back to it later), and click File, Open to open the file DataMod.cpp from the Legacy.bpr project ”without actually opening the project itself. Select the TDataBase , TSession , tblCustomer , tblOrders , dsCustomerOrders , and dspCustomerOrders . Copy them to the clipboard, move back to your Remote Data Module and paste them. See Figure 21.5 for the remote data module on my machine.
The only thing left to do now is to compile and run the DataSnap server application. This will register it on my local machine, so I can connect to it from the DataSnap client application using one of the many different connection types.
Let's return to the DataSnap client application. By this I mean the original Legacy.bpr application that currently uses a TLocalConnection component on the data module. We will now extend this data module with a new way to connect to the actual DataSnap Server that we just built. Because I'm testing on my local machine, the easiest way is to add a TDCOMConnection component. However, because my main point it to show how to use the TConnectionBroker component to easily switch between different TxxxConnection components, you can also pick a TSocketConnection or TWebConnection component at this time ”the choice is yours.
After you've dropped the TDCOMConnection component on the data module, you can open the drop-down combo box for the ServerName property (showing three DataSnap servers). In our example, we need to select DataSnapServer.RemoteDataMod42 as ServerName . If you set the Connected property of the TDCOMConnection component to true , the DataSnapServer will be started (it will pop up and also appear in the taskbar). If you set the Connection property to false again, the DataSnapServer should be shut down again.
After you've configured and tested the TDCOMConnection component, you can take the cdsCustomer TClientDataSet component and point it to the TDCOMConnection component instead of the TLocalConnection component. Whenever you now activate (or open) the cdsCustomer , you will use the new connection ”resulting in the DataSnapServer to be invoked (if it wasn't running already), and your DataSnap client to retrieve data from the remote data module on the DataSnapServer. Note that you do not have to change the cdsOrders TClientDataSet component because this one is simply connected to the tblOrders DataSetField inside cdsCustomer (which has already been taken care of).
For a single ClientDataSet, it didn't take long to switch from one TxxxConnection component to another. But a real-world situation often uses more than a single, sometimes even more than a few dozen or hundred TClientDataSets . In those situations, you need a little help from your friend the TConnectionBroker . Drop the TConnectionBroker component from the DataSnap tab onto the data module (see Figure 21.6). The TConnectionBroker has three important properties: Connection , Connected , and LoginPrompt . The most important one is the Connection property, which can be set to either the TLocalConnection or the TDCOMConnection component ”or to another TConnectionBroker (but a circular reference to the same TConnectionBroker itself is not allowed, of course). You can now click cdsCustomer again, and set its RemoteServer property to the TConnectionBroker . Whenever you need to switch between TxxxConnection components, you now only have to point a single RemoteServer property of the TConnectionBroker to another server. That's it, and you can even do this at runtime (make sure to close down the connection nicely , by setting Connected to false , before you attempt to switch from one Connection type to another). In fact, your client application can decide at runtime which connection to use, even before making the actual connection and opening the TClientDataSets . You only have to assign the right TxxxConnection component to the RemoteServer property of the ConnectionBroker component.
Using the combination of the TLocalConnection and the TConnectionBroker component enables you to write standalone applications that have the capability to switch to a multitier architecture when needed. You can even put a specific TxxxConnection component in it already (like we just did in our example) and make sure that the multitier edition of the application is tested thoroughly (on a few machines) before everyone switches over from the standalone version to the multitier version. After that happens, your end users will probably not even be aware of the change. (The actual presentation layer doesn't change ”and will not need to change because the interface to it remains connected to the TClientDataSet and TDataSource components on the lower-right corner of the data module ”independent of the fact whether we're connected to a remote server or just have a local connection.)
Top |