Connecting DataAbstract to a Datasnap server

dataabstract
delphi

(mahmoud EL SAWAH) #1

Hello everyone
We just acquired DataAbstract to create our iOS app.
The issue is our database server is a datasnap server, as i’m Kinda new to this whole DataAbstract thing, I need to understand if it’s possible to connect DataAbstract to the datasnap server?
If yes how would you recommend doing it?
If no, then what is the possible solution, as we don’t want to change the server (it handles our desktop client).
Thanks for your help.


(mahmoud EL SAWAH) #2

No one? :neutral_face:


(Carlo Kok) #3

Hi, you should be getting an answer shortly; my colleagues on the Data Abstract team were out of the office over the weekend.


(EvgenyK) #4

You can use our old article. It may contain some minor differences regarding to DA9 because it was created for DA6,


In this article will be shown how to migrate from Datasnap applications to DataAbstract application.

Migrating from a DataSnap application to Data Abstract_testcase.zip (335.2 KB)

Note: Datasnap projects were created in Delphi XE2 so some modification for earlier versions of Delphi can be required

Server-side

This datasnap server uses authentication, provides access to Customer table from Employee.gdb and allows to call custom server methods.
so DataAbstract server will contain two services: LoginService that will check authentication and DataService that will be main service.

Creating Server.RODL in Service Builder

It can be made via RemObjects SDK->Turn server into a RemObjects SDK Server

TurnServerIntoRemObjectsSDKServer

after confirmation, Service Builder will be launched.
For correct work, these two services should be created as descendants of base DataAbstract services. For this, DataAbstract4.RODL should be imported into server.RODL:


After this, two new services should be created: Loginservice with SimpleLoginService as the ancestor and DataService with DataAbstractService as the ancestor. Also create two operations in DataService:

 function EchoString(Value: WideString): WideString;
 function ReverseString(Value: WideString): WideString;

Close Service Builder.

Generation of files from RODL

Call RemObjects SDK->Regenerate Units from RODL.
These files will be generated:

  • NewLibrary_Intf.pas
  • NewLibrary_Invk.pas
  • LoginService_Impl.pas
  • DataService_Impl.pas

ServerDataModule.pas

Create a new datamodule and drop to it:

  • TROIndyHTTPServer
  • TROBinMessage
  • TROInMemorySessionManager
  • TDADriverManager
  • TDAConnectionManager
  • TDAIBXDriver

set this value in Object Inspector:

 DAConnectionManager1.DriverManager = DADriverManager1

Click on ROIndyHTTPServer1.Dispatchers in ObjectInspector and add new dispatcher
ROIndyHTTPServer_BinDispatcher
also create onCreate event:

procedure TDataModule1.DataModuleCreate(Sender: TObject); begin ROIndyHTTPServer1.Active := True; end;

LoginService_Impl.pas

Select LoginService_Impl.pas.

Add ServerDataModule into uses section and set in Object Inspector

 LoginService.SessionManager = ServerDM.ROInMemorySessionManager1. 

Two events are required in this unit:

procedure TLoginService.SimpleLoginServiceLogin(Sender: TObject; aUserID,
  aPassword: UTF8String; out aUserInfo: UserInfo;
  var aLoginSuccessful: Boolean);
begin
  aLoginSuccessful := (aUserID <> '') and (aUserID = aPassword);
  if aLoginSuccessful then CreateSession;
end;

procedure TLoginService.SimpleLoginServiceLogout(Sender: TObject);
begin
  DestroySession;
end;

DataService_Impl.pas

Select DataService_Impl.pas
drop

  • TDASchema
  • TDABin2DataStreamer

set properties in ObjectInsector:

 DataService.ServiceDataStreamer = DABin2DataStreamer1
 DataService.ServiceSchema = DASchema1
 DataService.SessionManager = ServerDM.ROInMemorySessionManager1
 DataService.RequiresSession = True
 DASchema1.ConnectionManager = ServerDM.DAConnectionManager1

and copy content of these methods from ServerMethodsUnit1.pas:

function TDataService.EchoString(const Value: UnicodeString): UnicodeString;
begin
  Result := Value;
end;

function TDataService.ReverseString(const Value: UnicodeString): UnicodeString;
begin
  Result := StrUtils.ReverseString(Value);
end;

Dbl click on DASchema1, Schema Modeler will be launched.

create a new connection to Employee.gdb and CUSTOMER table:


Close Schema Modeler.

Compile and launch server.

Server-side is ready now.

Client-side

Client side contains login/logout buttons, grid that shows table and two buttons which call custom servers methods.

Select client.dpr and add NewLibrary_Intf.pas from server project to client.dpr

ClientDataModule.pas

Add a new datamodule

Drop

  • TROWinInetHTTPChannel
  • TROBinMessage
  • TRORemoteService
  • TDARemoteDataAdapter
  • TDABin2DataStreamer
  • TDAVCLReconcileProvider
    and set properties in Object Inspector:
 ROWinInetHTTPChannel1.TargetURL = http://localhost:8099/bin
 RORemoteService1.Channel = ROWinInetHTTPChannel1
 RORemoteService1.Message = ROBinMessage1
 RORemoteService1.ServiceName = DataService
 DARemoteDataAdapter1.DataStreamer = DABin2DataStreamer1
 DARemoteDataAdapter1.RemoteService = RORemoteService1
 DARemoteDataAdapter1.ReconcileProvider = DAVCLReconcileProvider1

click on “Create Data Tables…” in popup menu of DARemoteDataAdapter1.
Login Screen will be shown, type ‘‘UserID=test;Password=test’’ in aLoginString and create CUSTOMERS table in next dialog.

MainForm.pas

Add NewLibrary_intf and ClientDataModule into uses section.
Replace old methods in MainForm:

procedure TForm2.Button1Click(Sender: TObject);
begin
  DataModule1.tbl_CUSTOMER.Close;
  DataModule1.tbl_CUSTOMER.Open;
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
  DataModule1.tbl_CUSTOMER.ApplyUpdates;
end;

procedure TForm2.Button3Click(Sender: TObject);
begin
  ShowMessage((DataModule1.RORemoteService1 as IDataService).EchoString(Edit3.Text));
end;

procedure TForm2.Button4Click(Sender: TObject);
begin
  ShowMessage((DataModule1.RORemoteService1 as IDataService).ReverseString(Edit3.Text));
end;

procedure TForm2.Button5Click(Sender: TObject);
begin
 if not CoLoginService.Create(DataModule1.ROBinMessage1,DataModule1.ROWinInetHTTPChannel1).LoginEx(
     UTF8Encode(Format('UserID=%s;Password=%s',[Edit1.Text,Edit2.Text]))) then
   showMessage('Problems with login');
end;

procedure TForm2.Button6Click(Sender: TObject);
begin
  DataModule1.tbl_CUSTOMER.Close;
  CoLoginService.Create(DataModule1.ROBinMessage1,DataModule1.ROWinInetHTTPChannel1).Logout;
end;

Client side is ready.

P.S. Don’t forget to remove old datasnap units from projects

Migrating from a DataSnap application to Data Abstract_testcase.zip (335.2 KB)


Installation failure on Dephi IDE
(mahmoud EL SAWAH) #5

Hi thanks for your answer EvgenyK
I already saw this article, it talks about converting the Datasnap server to a DataAbstract server which we would like to avoid, as we don’t want to change the client application, is there a way to connect the DataAbstract client to the Datasnap server without converting it? or at least having the Datasnap and DataAbstract Both side by side on the same server app?
thanks again


(EvgenyK) #6

Data Abstract client can’t connect to DataSnap server as is because DataAbstract client sends requests in another format in comparing with Datasnap, but you can have DataSnap server and DataAsbtract server in one app - they can use different ports.

Pls review the RO\DataSnap sample - it demonstrates how to use DataSnap technology with Remoting SDK server.


(mahmoud EL SAWAH) #7

Thanks
I will look into that


(mahmoud EL SAWAH) #8

can i get this article from the old wiki How to Write a Service Using DataSnap Classes (Delphi)
thanks


(EvgenyK) #9

This article shows how you can use DataSnap classes to retrieve data from a database using a remote client and send back changes to update the database. A sample project is provided, which you can download via the link at the bottom of the article.

Start by opening the CDSServerSample.bpg project group.

Note: you will get the most from this article if you examine the project in your own system, but you will still get a reasonable idea of the process without doing so.

The server

The server basically consists of a main form and a data module that contains the implementation of the service (CDSService) used in this example.

The CDSService is defined as follows:

ICDSService = interface
  ['{1F0A5C88-4B51-4D74-B2A9-C850E25E309A}']
  function GetData: Binary;
  procedure UpdateData(const Delta: Binary);
end;

The GetData method will read all the data contained in the DBDEMOS table employee.db and the Binary result will be passed to a TClientDataset used by the client. That TClientDataset will be bound to a TDBGrid and will allow users to browse and edit the data in a disconnected fashion.

The UpdataData method will then be used by the client to propagate the changes back to the server which, in turn, will update “employee.db”. If the update fails, an exception will be sent back to the client and no data will be stored.

The data module contained in the unit CDSService_Impl.pas looks like this:
RO20_01

As you can see, there is really nothing special in this data module except for the DataSetProvider component. This component, part of the DataSnap framework, is responsible for generating the data returned by the GetData method, and for resolving the changes sent by the client via UpdateData.

This is the implementation of TCDSService’s GetData:

function TCDSService.GetData: Binary;
var
  recsout: integer;
begin
  result := BinaryFromVariant(
  DataSetProvider.GetRecords(-1, recsout, MetaDataOption + ResetOption));
end;

BinaryFromVariant is a utility routine that we provide in the uROBinaryHelpers.pas unit. It simply converts OleVariants to Binary (which is a simple TMemoryStream). Refer to the Delphi help file for more information about TDatasetProvider.GetRecords.

Here is the implementation of the UpdateData method:

procedure TCDSService.UpdateData(const Delta: Binary);
var
  errcnt: integer;
begin
   DataSetProvider.ApplyUpdates(VariantFromBinary(Delta), 0, errcnt);

  if (errcnt>0) then
    raise Exception.CreateFmt('There were %d errors', [errcnt]);
end;

Nothing more than transforming the delta of changes received into a variant using VariantFromBinary, and invoking the right method of the dataset provider.

This is all that is needed for the server side.

The client

The client form looks like this:

RO20_02

This is the code attached to the “Get Data” button:

procedure TClientForm.Button1Click(Sender: TObject);
var
  svc: CDSService;
  data: Binary;
begin
  svc := CoCDSService.Create(ROSOAPMessage, ROWinInetHTTPChannel1);
  try
    data := svc.GetData;
    ClientDataSet1.Data := VariantFromBinary(data);
  finally
    data.Free;
  end;
end;

In the first line we create a proxy to our remote service, then we invoke its GetData method and, using the functions of uROBinaryHelpers.pas, we fill the TClientDataset.Data variant.

After this method executes, you will see the TDBGrid populated with all the records of the remote “employee.db” table.

This is the code attached to the “Update Data” button:

procedure TClientForm.Button2Click(Sender: TObject);
var
  svc: CDSService;
  delta: Binary;
begin
  if (ClientDataSet1.ChangeCount= 0 ) then begin
    MessageDlg('There are no changes pending', mtError, [mbOK], 0);
    Exit;
  end;
  delta := NIL ;
  try
    delta := BinaryFromVariant(ClientDataSet1.Delta);
    svc := CoCDSService.Create(ROSOAPMessage, ROWinInetHTTPChannel1);
    svc.UpdateData(delta);
  finally
    delta.Free;
  end;
end;

Conclusions

This example has shown how easy it is for a system written with the Remoting SDK to manually take advantage of DataSnap’s classes.


(mahmoud EL SAWAH) #10

Hello EvgenyK!
I looked into the sample you sent me
I’m having troubles understanding how I can have datasnap connectivity along with DataAbstract connectivity in the same server without heavily modifying the server and modifying my current client which I wanted to avoid.
Please any help or indication would be appreciated.


(EvgenyK) #11

Hi,
Can you describe your current server and what you want to reach finally?
You can post this info to support@ in case this is private info


(mahmoud EL SAWAH) #12

The current server is a basic SOAP datasnap server (it will evolve to a REST datasnap)
We have a client (pc) that connects to this server and everything is working perfectly, we decided to create a natif iOS App on Xcode and not on Rad studio, and we bought DA to do that, but we realised that it could not connect to datasnap directly and we needed to modify the server ( my original post).
We basically wanna include DA connectivety to the server without messing with the datasnap connection that works


(EvgenyK) #13

You can have DA server and DataSnap server in one app.
they can handle different ports.

You can create a new DAD server with wizard and then add created files to your project.
also you need to copy these lines from created project into your one:

{#ROGEN...
{$R RODLFile.res}

(mahmoud EL SAWAH) #14

Thanks EvgenyK we will try that,what is the DAD server for you? is there any topic created about that in the old wiki?


(EvgenyK) #15

DAD server = Data Abstract for Delphi server

so you can use our wizard, File->New->Other->Data Abstract->VCL Application (or FMX appliication)


(mahmoud EL SAWAH) #16

Hello EvgenyK
thanks again, but i need your answer on some important questions, it’s crucial cause it will decide if we will continue using DA or not:

1- Could you confirm if each datasets needs to be recoded using a Data Abstract compatible dataset, including the business logic behind these datasets, or do we only need to drop additinal components to open a new DA interface ?

The problem i am having is that i want to be sure i won’t have to recode the server again, or that i won’t have to re-implement all lmy buisness logic, etc…


(EvgenyK) #17

what business logic you have: client-side or server-side?
can you describe it?

Data Abstract supports both (client-side and server-side) business logic. this logic can be coded directly in code or can be added via JavaScript code inside Schema Modeler.

Data Abstract tables and fields have almost the same properties/methods as usual TDataset and TField so pretty sure you can use your client-side business logic w/o big changes.
also tables have the Dataset property and fields have the BoundedField property that gives access to nested TDataset/TFields accordingly


(mahmoud EL SAWAH) #18

I will put this here so people can have it if they are in the same case:

we have our PC client /server that was developed on Delphi to perform operations on a firebird database.
Now we need to develop an iOS app that would connect to the same server, and perform some of the same operations but on a mobile Device.
The server includes the Buisness logic and includes all the database management, it’s a datasnap SOAP server.
The iOS app would have to connect to that server and respect all the restrictions the server impose.
Now the problem we are facing is that either we develop the iOS app on RAD Studio and it works with our sever without any modifications.
OR we code on Xcode and use DataAbstract to manage the middle tier connection.
The problem is the Server, we absolutely need to connect to the same server and respect everything that has been coded into it since the beginning.
Adding DataAbstract to the server and making it a hybrid server would be a perfect solution, only if you could confirm if each datasets needs to be recoded using a Data Abstract compatible dataset, including the business logic behind these datasets, or if we only need to drop additinal components to open a new DA interface ?

EvgenyKstaff

1d

Data Abstract server can connect to Firebird w/o any problem.
you can connect to FireBird via your favorite Direct Component Library (DAC). DA supports most popular DACs (FireDAC/AnyDAC, IBX, IBDAC, UniDAC, etc).

can you give some code snippet with your business logic?

as a temporary solution during migration, you can register all your datasnap datasets in DataAbstractService.ExportedDataTables collection so they will work like they were declared in Schema Modeler. In this case, all business logic will be reused in case it was used via dataset’s events.

If your business logic was implemented something else, like via events of datasnap module, it may not work.

mahmoud_EL_SAWAH

1d

We are not migrating as we still need datasnap to connect our PC client to the server.
We need both, and we need them to cohabitate.

mahmoud_EL_SAWAH

1d

We don’t want to code all our settings in the server for them to work with DataAbstract, I thought DA would behave like a door to the server and then the server manages the rest as it usually do

EvgenyKstaff

1d

DataAbstract can transport select, insert, update, delete requests from client to server and later to DB server. this is no problem at all.

in your case, as I understand, the problem with porting server-side business logic from Datasnap to DA part.
can you give to me some sample of your business logic and I will say how it can be ported

mahmoud_EL_SAWAH

1d

I have this question still unanswered, could you confirm if each datasets needs to be recoded using a Data Abstract compatible dataset, including the business logic behind these datasets, or if we only need to drop additinal components to open a new DA interface ?

EvgenyKstaff

1d

if you declare your tables in Schema modeler (it can be easily done with drag&drop), then we only need to drop additinal components to open a new DA interface

if you want to add some business logic then you need add it manually to server-side for DataAbstract service.


(mahmoud EL SAWAH) #19

Could you give us pointers what are the additional components needed to be dropped to open a new DA interface in the Datasnap server?
A tutorial would be mush appreciated :+1:t3:


(EvgenyK) #20

use our wizard: File->New->Other->Data Abstract->Client module for VCL|FMX application
it will ask you for server address and will create required Data Abstract components