Borland Delphi 2005 en .NET Remoting |
In dit artikel wil ik gebruikmaken van Delphi 2005 om te illustreren welke verbetereningen er aan Delphi zijn toegevoegd, met name op database gebied en in het kader van .NET Remoting. Ik zal een viertal nieuwe componenten gebruiken die ons in staat stellen om heterogene (verschillende) databases aan te spreken, en dat ook nog eens in verschillende tiers (dus distributed computing). En dat alles natuurlijk met hooguit een enkele regel code hier en daar - de rest gaat geheel volgens de Delphi manier via het zetten van properties (en de infrastructuur achter de schermen).
ADO.NET en BDP
Voor WinForms en ASP.NET Web Forms toepassingen is ADO.NET de standaard .NET manier om databases te benaderen.
Ook Delphi 2005 ondersteunt ADO.NET, al zijn niet alle ADO.NET componenten ook meteen zichtbaar in de Tool Palette.
Een normale installatie van Delphi 2005 laat in de Data Components category alleen de SqlDataAdapter, SqlConnection, DataView, DataSet en SqlCommand zien.
De drie SqlXXX componenten zijn SQL Server specifieke componenten, en missen eigenlijk nog de SqlCommandBuilder (die is ook te installeren).
Om dit te doen - en de andere ADO.NET componenten te zien - moet je even de Component | Installed .NET Components dialoog starten.
Het duurt even, terwijl Delphi alle aanwezige componenten opzoekt op je systeem, en laat zien.
Als je in de lijst naar de SqlXXX componenten zoekt, zie je de SqlCommandBuilder ook staan, zoals te zien in de volgende figuur.
Door het vinkje aan te zetten krijg je ook dit component in de Delphi 2005 Tool Palette te zien.
Daarnaast kun je ook de andere standaard ADO.NET provider componenten selecteren, zoals de OdbcXXX, OleDbXXX en OracleXXX componenten.
Ieder van deze verzameling ADO.NET componenten is specifiek voor een database (of connectie) soort, namelijk resp. SQL Server, ODBC, OLEDB of Oracle.
Borland heeft het ADO.NET Provider model ook geïmplementeerd, maar dan in de vorm van één verzameling componenten: de BDP (Borland Data Provider) componenten, met een BdpConnection, BdpDataAdapter, BdpCommand, BdpCommandBuilder, etc.
Deze componenten hebben als grootste voordeel dat ze database onafhankelijk zijn, en door het specificeren van de ConnectionString (in de BdpConnection) kunnen kiezen met welke database ze praten.
Dat kan zowel SQL Server als Oracle weer zijn, maar ook IBM DB2, Sybase, InterBase of zelfs MS Access (behalve als je een database password op je Access MDB hebt staan, dan werkt het niet - een normale user/password combinatie werkt wel overigens).
Een ander voordeel van de BDP componenten is dat ze het mogelijk maken om "live" data tijdens design-time te kunnen laten zien, alhoewel we dat met Delphi 2005 ook kunnen via de normale ADO.NET componenten (zoals ik straks zal latern zien).
De Borland Data Provider componenten werden in C#Builder geïntroduceerd, kwamen ook al in Delphi 8 for .NET voor, maar zijn nu uitgebreid met een aantal nieuwe componenten waar ik wat aandacht aan wil besteden.
DataSync en DataHub
Twee van de nieuwe componenten die in de Borland Data Provider category terug te vinden zijn, zijn de DataSync en DataHub.
Deze twee componenten zijn echter niet beschikbaar in Delphi 2005 Professional (maar alleen in Enterprise of Architect).
De DataSync is een plaats waar je verschillende DataAdapters samenvoegt - zowel van de Borland Data Provider (dus BdpDataAdapters) als standaard ADO.NET (dus bijvoorbeeld een SqlDataAdapter of OracleDataAdapter).
De DataSync verzamelt de DataAdapters, en is in staat om de updates naar de verschillende DataAdapters in één keer uit te voeren.
De DataHub wordt verbonden met een DataSync - direct of indirect via .NET Remoting - en wordt gebruikt om het resultaat van de SelectCommands van de DataAdapters in een .NET DataSet te stoppen.
Alles bijelkaar, zodat je uiteindelijk dus bijvoorbeeld een master-detail relatie kunt maken waarbij de master uit SQL Server komt en de detail uit InterBase.
Als demonstratie moet je eerst een nieuwe WinForms toepassing starten met Delphi 2005.
Zet vervolgens zowel een DataSync als een DataHub component in het non-visual components deel van de WinForms designer.
Om nu een tweetal DataAdapters in de DataSync te stoppen kies ik voor een ADO.NET (Sql Server) alsmede een BDP (InterBase) variant.
Zet een SqlConnection component op de designer, en dubbel-klik op de ConnectionString property in de Object Inspector.
Dit laat de Data Link Properties dialoog zien (die overigens niet beschikbaar was in Delphi 8 for .NET - daar moest je de ADO.NET ConnectionString nog met de hand invoeren).
Als voorbeeld wil ik met de Northwind database werken, bijvoorbeeld met de dbo.Employees tabel erin. Hiertoe moeten we er een SqlDataAdapter component bij zetten, en van dit component de SelectCommand property open klappen in de Object Inspector. Laat dan de Connection subproperty wijzen naar de SqlConnection component, en vul in de CommandText property de volgende query in:
SELECT * FROM EmployeesDit is voldoende om de records uit de Employees tabel van de Northwind database op te halen en te gebruiken.
Behalve deze ene DataProvider, die aan de enige DataAdapter hangt die we gebruiken, is het natuurlijk mogelijk om meerdere DataProviders toe te voegen, die elk met een andere DataAdapter verbonden zijn. Dat gaan we straks doen. Eerst even het simpele voorbeeld afmaken met de DataHub erbij. Selecteer nu de DataHub, en laat zijn DataPort property wijzen naar het DataSync component. In de DataSet property moeten we een verwijzing opnemen naar een .NET DataSet, dus moeten we er eentje op de form plaatsen (te vinden in de Data Components category van het Tool Palette). Nadat zowel de DataPort als de DataSet property van de DataHub een waarde hebben gekregen, kunnen we de Active property van de DataHub op True zetten. Dit heeft als gevolg dat van alle DataProviders in de lijst van de DataSync (ook al is het er maar eentje), de SelectCommand van de DataAdapter wordt uitgevoerd, en het resultaat in de DataSet komt, onder de gespecificeerde naam voor de DataTable. En het feit dat we nu dus een DataTable tijdens design-time hebben kunnen we gebruiken om ook met de SqlXXX ADO.NET componenten "live" data tijdens design-time te laten zien. Zet een DataGrid op de Form, en laat de DataSource property wijzen naar de DataSet component, en de DataMember property naar de naam van de DataTable (zoals je die eerder hebt opgegeven). Het resultaat zal "live" data tijdens design-time zijn, zoals we als Delphi ontwikkelaars al gewend waren sinds versie 1.
De grap is dat je in Delphi 8 for .NET alleen "live" data tijdens design-time kon krijgen door gebruik te maken van een BdpDataAdapter, en niet met een "eenvoudige" SqlDataAdapter. De DataSync en DataHub componenten hebben wat dat betreft het verschil tussen ADO.NET en BDP iets kleiner gemaakt (of in ieder geval één van de voordelen van BDP weggewerkt). Maar ze hebben tegelijkertijd een aantal voordelen toegevoegd, zoals de ondersteuning voor heterogene databases met de DataSync.
Heterogene Databases
Het begrip heterogene databases wil zeggen dat je tabellen gebruikt (en mogelijk zelfs combineert) uit verschillende databases; vaak zelfs verschillende DBMS systemen.
De voorbeeldtoepassing maakt op dit moment gebruik van de Employees tabel uit de SQL Server Northwind database.
We kunnen dit uitbreiden met de EMPLOYEE tabel uit de InterBase Employee database.
In de rechterbovenhoek van de IDE zit de project manager, die zijn ruimte deelt met de "Model View" en de "Data Explorer".
Klik op de tab van de Data Explorer en selecteer de InterBase connectie.
Zorg dat die naar de voorbeeld Employee.gdb database wijst, en open de Table knoop om vervolgens de EMPLOYEES tabel naar je WinForms designer te slepen.
Het resultaat is een tweetal nieuwe componenten in het non-visual component deel van de WinForms Designer: een BdpConnection en een BdpDataAdapter.
De BdpConnection wijst al naar de juiste InterBase database, en de BdpDataAdapter bevat al een query naar de EMPLOYEES tabel. Met de rechter muisknop op de BdpDataAdapter kun je de Data Adapter Configuration dialoog zien:
Hier kun je de vier SQL commands bekijken voor Select, Insert, Update en Delete. In dit geval hebben we alleen de Select maar nodig, dus je kan de rest uitzetten en op de "Generate SQL" knop drukken. Meer hoeven we niet te doen in de Data Adapter Configuration dialoog - ook niet het toekennen van een DataSet aan de BdpDataAdapter: dat is iets wat in Delphi 8 nuttig was, maar nu stoppen we er een DataSync en DataHub tussen. We moeten nog wel de BdpDataAdapter toevoegen aan de lijst van DataProviders van de DataSync. Dubbelklik weer op de DataProviders property van de DataSyn component om de dialoog van het volgende figuur te krijgen. Voeg een nieuwe DataProvider toe, laat die als DataAdapter naar de BdpDataAdapter wijzen, en deze keer kunnen we wel de waarde "Changed" opgeven voor de UpdateMode.
Het resultaat is dat iedere update die de DataSync zal uitvoeren op de data van de BdpDataAdapter zo optimaal mogelijk uitgevoerd zal worden: alleen de gewijzigde velden zullen voorkomen in het Update statement, en ongewijzigde velden zullen genegeerd worden.
Dit scheelt meer naarmate de records uit meer velden bestaan (en er relatief kleine wijzigingen worden doorgevoerd).
Als we nu de DataHub activeren (Active op True zetten), zal de DataSet zich vullen met twee DataTables: een voor iedere DataProvider van de DataSync component.
En de namen van de DataTables hebben we ook al van te voren kunnen opgeven (in eerdere figuren is dat nog resp. DataProvider1Table en DataProvuder2Table).
Je kan nu de DataMember property van het DataGrid laten switchen tussen DataProvider1Table en DataProvider2Table.
Dit kan ook in source code met de volgende code:
procedure TwinForm.Button1_Click(sender: System.Object; e: System.EventArgs); begin if DataGrid1.DataMember = 'DataProvider1Table' then DataGrid1.DataMember := 'DataProvider2Table' else DataGrid1.DataMember := 'DataProvider1Table'; end;Behalve het switchen van de inhoud van het DataGrid, is dit natuurlijk pas een echt zinvol voorbeeld als we ook wijzigingen kunnen maken in het DataGrid en die wijzigingen kunnen doorsturen naar de verschillende databases. Dat kan zelfs met één regel code (die ervoor zorgt dat de wijzigingen zowel naar de SQL Server als naar de InterBase database worden gestuurd: voor iedere DataTable in de DataSet zal de bijbehorende DataAdapter gebruikt worden om updates, inserts en deletes uit te voeren op de betreffende database.
procedure TWinForm.Button2_Click(sender: System.Object; e: System.EventArgs); begin DataSync1.SaveData(DataSet1); end;Het resultaat is een toepassing die twee tabellen gebruikt uit verschillende databases, en die met één regel code kan updaten. Het is echter een zware toepassing, want hij gebruikt zowel SQL Server als InterBase, en ik wil de rest van dit artikel besteden aan het splitsen van de toepassing in twee delen: een zwaar server deel (dat met SQL Server en InterBase praat) en een thin-client deel dat niet weet met welke databases er gewerkt wordt, maar gewoon een gevulde DataSet krijgt om mee te werken.
RemoteServer
Als architectuur maak ik nu gebruik van .NET Remoting, met behulp van de RemoteServer en RemoteConnection componenten die in Delphi 2005 zitten.
De toepassing die we tot nu toe hebben kan gebruikt worden om de .NET Remoting server van te maken.
Gooi de Buttons, het DataGrid, de DataSet en de DataHub van het WinForm af, en vervang die door een RemoteServer component.
Het WinForm zelf is nu leeg, op de non-visual componenten DataSync, SqlConnection, SqlDataAdapter, BdpConnection, BdpDataAdapter en RemoteServer na.
Er moeten in ieder geval een tweetal properties gezet worden van de RemoteServer component: allereerst moet de DataSync property naar het DataSync component gaan wijzen, en ten tweede is het erg handig om de AutoStart property op True te zetten (zodat de .NET Remoting server meteen gaat wachten op binnenkomende requests zodra de toepassing start).
Daarnaast kun je nog het ChannelType instellen.
Default staat dat op Http, wat een SOAP message formaat tot gevolg heeft, maar je kan het wijzigen in Tcp met een Binary message formaat.
Dit moet je straks in de client bij de RemoteConnection ook precies zo instellen, uiteraard.
Wat de client ook moet weten is de URL (die default op RemoteServer1 staat) en het Port nummer, dat standaard op 8000 staat.
Je mag die uiteraard wijzigen, maar moet ervoor zorgen dat de RemoteConnection ze precies zo overneemt.
Na al deze aanpassingen kun je de toepassing opnieuw compileren en draaien: de .NET Remoting server bestaat nu uit een lege form, die met twee databases werkt, en wacht op binnenkomende connecties.
Laat de toepassing draaien, maar sluit het project in Delphi en begin een nieuw WinForms project waar we de .NET Remoting (thin-)client van maken.
Het leukste is om deze .NET Remoting client op een andere machine te draaien (eentje die wel via een netwerkverbinding in verbinding staat met de originele machine - waarbij de firewall de betreffende poort moet openzetten, of de machines elkaar moet laten accepteren), zoals een Windows 2000 machine in mijn geval.
RemoteConnection
Op de nieuwe lege WinForm zetten we nu als eerste een RemoteConnection component neer.
Deze is verantwoordelijk voor de communicatie met de .NET Remoting server.
Hiervoor moeten we een aantal properties zetten.
Allereerst de URI, die default op RemoteServer1 moet komen te staan (er staat default alleen RemoteServer, zonder de 1 erachter), of op de waarde van de URI die je bij de RemoteServer hebt gebruikt.
Ook de Port en het ChannelType moet exact hetzelfde zijn als bij de RemoteServer.
Vervolgens moet je bij de Host de computernaam of IP-adres van de server machine opgeven.
Dat is default localhost als je met jezelf speelt, maar in mijn voorbeeld heeft de server een IP-adres 192.168.92.41 (in mijn interne netwerk thuis).
Als je alles hebt ingesteld kun je de proef op de som nemen door de drop-down combobox voor de ProviderType property te openen.
Als Delphi nu een halve minuut wacht alvorens te vertellen dat er geen verbinding gemaakt kon worden, dan heb je dus nog iets verkeerd gedaan of vergeten.
Als het wel lukt kun je kiezen uit een lijst met maar een keuze: de Borland.Data.Provider.DataSync (wellicht dat het in de toekomst mogelijk is hier je eigen types of third-party types aan toe te laten voegen).
Als de RemoteConnection een verbinding met de .NET Remoting server kan maken, hebben we alleen nog een DataHub en DataSet nodig (om de DataTables op te halen en in te bewaren), alsmede een DataGrid en een tweetal Buttons.
De DataPort property van de DataHub moet nu niet naar een DataSync component wijzen (want die is er niet in de client), maar naar de RemoteServer component.
Deze laatste zal ervoor zorgen dat de DataHub toch met de DataSync kan praten - maar dan via een RemoteConnection-RemoteServer verbinding ertussen.
Als tweede property moet de DataSet proeprty van de DataHub gaan wijzen naar de DataSet.
Dat is alles - de Active property hoef je niet op True te zetten tijdens design-time.
Dat is zelfs beter van niet, want anders kun je je project niet meer openen als de .NET Remoting server niet te bereiken is.
Net als DataSnap clients die je met een inactieve ClientDataSet moet opslaan.
Het activeren van de DataHub tijdens runtime kunnen we in de WinForm_Load doen, als volgt:
procedure TWinForm3.TWinForm3_Load(sender: System.Object; e: System.EventArgs); begin DataHub1.Active := True; DataGrid1.DataSource := DataSet1; DataGrid1.DataMember := 'DataProvider1Table'; end;Deze code zorgt er meteen voor dat het DataGrid gevuld is met de data uit de eerste DataTable (met de default namen die eerder gebruikt zijn).
procedure TWinForm3.Button1_Click(sender: System.Object; e: System.EventArgs); begin if DataGrid1.DataMember = 'DataProvider1Table' then DataGrid1.DataMember := 'DataProvider2Table' else DataGrid1.DataMember := 'DataProvider1Table'; end;Blijft er nog een Button over: voor het updaten van de databases, oftewel het opsturen van de wijzigingen in de DataTables van de DataSet. De DataSync is niet langer toegankelijk, maar de kunnen nu de DataHub gebruiken die wederom via de RemoteConnection-RemoteServer laag de DataSync zal bereiken met de wijzigingen. Wel moeten we ervoor zorgen dat de DataSet zelf ook de wijzigingen heeft geaccepteerd (als de ApplyChanges zelf niet fout ging), als volgt:
procedure TWinForm3.Button2_Click(sender: System.Object; e: System.EventArgs); begin try DataHub1.ApplyChanges; DataSet1.AcceptChanges except on Ex: Exception do MessageBox.Show(Ex.Message) end end;Het resultaat is te zien in de laatste figuur: een thin-client die in mijn geval draait onder Windows 2000 (een andere machine dan waar de .NET Remoting server op draait die met SQL Server en InterBase praat).
Ik kan wijzigingen maken in het DataGrid, switchen naar de andere DataTable en wederom wijzigingen maken.
Zolang ik de client niet afsluit zullen de wijzigingen in de DataSet bewaard blijven (die kan je desgewenst zelfs serialiseren als XML voor het geval de .NET Remoting server (tijdelijk) niet meer beschikbaar is, en je je wijzigingen niet verloren wilt laten gaan).
Met een druk op de Update knop worden de wijzigingen dan in één keer naar de .NET Remoting server verstuurd.
Dit mag misschien minder handig lijken dan het automitisch versturen van de wijzigingen, maar heeft als voordeel dat we "undo" mogelijk kunnen maken (zolang de updates nog niet verstuurd zijn), en dat het efficienter is om enkele updates te bewaren alsvorens die in één keer naar de server te sturen.
Anders moet de client toepassing na iedere wijziging een pakketje naar de server sturen, en dat komt de performance niet echt ten goede.
Het mooie van de thin-client is dat hij onafhankelijk is van databases (en drivers): je hoeft geen database drivers op de client te installeren (alleen maarop de machine waar de .NET Remoting server draait).
Samenvatting
In dit artikel heb ik het gebruik van vier nieuwe BDP componenten gedemonstreerd: de DataSync, DataHub, RemoteServer en RemoteConnection.
We kunnen met de DataSync component op eenvoudige wijze meerdere databases gebruiken in onze toepassingen, en ook "live" data tijdens design-time laten zien voor generieke ADO.NET providers (en niet alleen voor de BDP).
De DataSync-DataHub combinatie kan opgesplits worden in een multi-tier situatie door er een RemoteServer en RemoteConnection component tussen te zetten op respectievelijk de .NET Remoting server en (thin) client.
Als communicatieprotocol kunnen we gebruik maken van Http (en SOAP messages) of Tcp (en Binary messages).
En uiteraard zijn er geen royalities verschuldigd voor het bouwen van distributed applicaties in Delphi 2005 op deze manier.
Wie meer wil weten over Delphi 2005 en ADO.NET of BDP (al dan niet in combinatie met .NET Remoting) is welkom op een van mijn trainingsdagen.