Microsoft AlwaysOn failover solution and Delphi - delphi

I'm trying to make a Delphi application to work with AlwaysOn solution. I found on Google that I have to use MultiSubnetFailover=True in the connection string.
Application is compiled in Delphi XE3 and uses TADOConnection.
If I use Provider=SQLOLEDB in the connection string, application starts but it looks like MultiSubnetFailover=True has no effect.
If I use Provider=SQLNCLI11 (I found on Google that OLEDB doesn't support AlwaysOn solution and I have to use SQL Native client) I get invalid attribute when trying to open the connection.
The connection string is:
Provider=SQLOLEDB.1;Password="password here";Persist Security Info=True;User ID=sa;Initial Catalog="DB here";Data Source="SQL Instance here";MultiSubnetFailover=True
Do I have to upgrade to a newer version on Delphi to use this failover solution or is something that I'm missing in the connection string?

I am currently using XE2 with SQL Server AlwaysOn. If you read the documentation you will see that AlwaysOn resilience events will cause your database connection to fail and you need to initiate a new one.
If a SqlClient application is connected to an AlwaysOn database that
fails over, the original connection is broken and the application must
open a new connection to continue work after the failover.
I've dealt with this via the simple expedient of overriding the TAdoQuery component with my own version which retries the connection after getting a connection failure. This may not be the proper way to do this but it certainly works. What it does is override the methods invoked for opening (if the query returns a result set) or executes the SQL (otherwise) and if there is a failure due to connection loss error tries again (but only once). I have heavily tested this against AlwaysOn switch overs and it works reliably for our configuration. It will also react to any other connection loss events and hence deals with some other causes of queries failing. If you are using a component other than TAdoQuery you would need to create similar overrides for that component.
It is possible this can be dealt with in other ways but I stopped looking for alternatives once I found something that worked. You may want to tidy up the uses statement as it clearly includes some stuff that isn't needed. (Just looking at this code makes me want to go away and refactor the code duplication as well)
unit sptADOQuery;
interface
uses
Windows, Messages, SysUtils, Classes, Db, ADODB;
type
TsptADOQuery = class(TADOQuery)
protected
procedure SetActive(Value: Boolean); override;
public
function ExecSQL: Integer; // static override
published
end;
procedure Register;
implementation
uses ComObj;
procedure Register;
begin
RegisterComponents('dbGo', [TsptADOQuery]);
end;
procedure TsptADOQuery.SetActive(Value: Boolean);
begin
try
inherited SetActive(Value);
except
on e: EOleException do
begin
if (EOleException(e).ErrorCode = HRESULT($80004005)) then
begin
if Assigned(Connection) then
begin
Connection.Close;
Connection.Open;
end;
inherited SetActive(Value); // try again
end
else raise;
end
else raise;
end;
end;
function TsptADOQuery.ExecSQL: Integer;
begin
try
Result := inherited ExecSQL;
except
on e: EOleException do
begin
if (EOleException(e).ErrorCode = HRESULT($80004005)) then
begin
if Assigned(Connection) then
begin
Connection.Close;
Connection.Open;
end;
Result := inherited ExecSQL; // try again
end
else raise;
end
else raise;
end;
end;
end.

Related

Delphi 10 Seattle Datasnap error: "Operation failed. Connection was closed."

I created a stand-alone Datasnap TCP/IP server using the Wizard. I selected sample methods (echostring and reversestring). I saved the server and ran it. Then I created a client application, and using the file-new-other, added a ClientModule to that client project, along with the ClientClasses unit. On the main form. I added a button. On the button's onclick event handler, I added the following code:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ClientModule1.SQLConnection1.Connected then
begin
Button1.Text := 'Open';
ClientModule1.SQLConnection1.Close;
end
else
begin
Button1.Text := 'Close';
// ClientModule1.SQLConnection1.Open;
ClientModule1.ServerMethods1Client.ReverseString('myteststring');
end;
end;
The purpose here is to simulate a situation where the client is logging into and logging out of the server regularly rather than keeping a connection. This is especially important on apps deployed to mobile.
You can see I commented out the Connection.Open, because the first call to the ServerMethods1client opens the connection. The generated code is shown here:
function TClientModule1.GetServerMethods1Client: TServerMethods1Client;
begin
if FServerMethods1Client = nil then
begin
SQLConnection1.Open;
FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, FInstanceOwner);
end;
Result := FServerMethods1Client;
end;
Now the problem arises. On first click to the button, the connection is opened, and the method is called. On the second click to the button, the connection is closed.
On the 3rd click, an exception is raised "Operation Failed. Connection was Closed" is raised from with the TDBXCommand code.
As a workaround, I tried this:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ClientModule1.SQLConnection1.Connected then
begin
Button1.Text := 'Open';
ClientModule1.SQLConnection1.Close;
ClientModule1.ServerMethods1Client := nil;
end
else
begin
Button1.Text := 'Close';
// ClientModule1.SQLConnection1.Open;
ClientModule1.ServerMethods1Client.ReverseString('myteststring');
end;
end;
This does sort-of solve the problem, since the ClientModule1's FServerMethods1Client instance is reset so the create code runs again like it did on the first run.
The only other problem now, is (I am using Eurekalog) it creates a memory leak.
What am I doing wrong? What's the right way to connected/disconnect from a Datasnap server repeatedly without restarting the app?
The reason for the first error is that the code that binds the client side proxy (which allows server methods to be called) is tied to the local SQL connection. Note the call to create the proxy class:
FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, ...)
The underlying DBExpress connection is passed by reference, and the proxy class uses that connection to call the server. You closed and re-opened the connection, but the underlying DBExpress connection that ServerMethodsClient1 was using has been destroyed. Thus, you receive the "Connection was closed" exception. The connection that ServerMethodsClient1 was using has been closed. You have to recreate ServerMethodsClient1 as you did in your second example.
I can't answer your second question, as I believe it is ARC specific. For a VCL DataSnap app, I would call ServerMethodsClient1.Free rather than setting it to nil. Based on my very, very limited understanding of Delphi's ARC implementation (which is all from the newsgroups), I believe you should call ServerMethodsClient1.DisposeOf, since the class descends from TComponent
But I'm not sure about that. I'm sure someone will jump on here that understands ARC and the proper solution to destroy the object rather than having a memory leak.
In my Android FMX implementation, I only call servermethods to get stuff done. (ie I don't use Datasnap data components). There's too much uncontrolled data transmission overhead to the Datasnap architecture to contemplate anything else realistically on a mobile device... To get around it (and not have memory leaks), I now create local instances of the TServermethods1Client as and when I need them and free them in context:
function TClientModule1.PostTheLog: Boolean;
var
Server: TServerMethods1Client;
begin
Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
try
UserID := Server.GetUserID;
...
finally
Server.Free;
end;
end;
Now the ClientModule1.SQLConnection1 can be connected and disconnected at will (preferably connected just before any call to a servermethod, and disconnected thereafter) and no further issues arise.
Which then begs the question: In which ideal world would the publicly accessible ServerMethods1Client actually be useful?

Sharing an ADO Connection across a DLL boundary

We would like to share an ADOConnection across a DLL boundary (Delphi to Delphi at the moment, though could also be C# to Delphi in the near future).
As we would like the flexibility to call the DLL from c# in future, we were hoping to be able to define the DLL call using _Connection as a parameter. Something like:
procedure DoStuff (ADOConnection: _Connection)
var
InnerConnection: TADOConnection;
begin
InnerConnection := TADOConnection.create(nil);
try
InnerConnection.ConnectionObject := ADOConnection;
DoMoreStuff(InnerConnection);
finally
InnerConnection.free;
end;
end;
Unfortunately, the TADOConnection destructor code closes the connection passed into it, which is an unwanted side-effect. Adding
InnerConnection.ConnectionObject := nil
prior to the free doesn't do anything, as it's caught by
if Assigned(Value) = nil
in TADOConnection.SetConnectionObject, which results in the call not doing anything.
Is there a better way of achieving this? Passing the connection string is an alternative, but would mean that we would have to deal with username/password issues and encryption across the boundary. Passing the TADOConnection is another option, but that prevents calling from other languages.
Edit: For clarity, the Username/Password of the original TADOConnection object is set using the .Open routine, so these details aren't in the connection string (in fact, the wrong username is usually stored, as it's the name used to 'test connection' in the MS UDL editor)
You can try this way:
type TInit_StFattDLL = procedure( var DataBase:TAdoConnection);
var Init_StFattDLL:TInit_StFattDll;
The caller is:
Function ConnectDll():Boolean;
var
handleDll:THandle;
begin
handleDll := LoadLibrary('mydll.DLL');
#Init_StFattDLL := GetProcAddress(handleDll , 'myConnectFunction');
if #Init_StFattDLL <> nil then
begin
Init_StFattDLL(ADOConnection1);
result:=true;
end
else
result:=false;
end;
into the the dll put the following:
in the project file put the exports:
Exports myConnectFunction;
global section:
var Database:TAdoConnection;
the exported procedure is the following:
procedure myConnectFunction( var MyDataBase:TAdoConnection);export;
begin
Database:=MyDataBase;
end

How to insert records with DataSnap

In many tutorials i read how to select data from a database in a datasnap client, p.e. to complete a dbgrid.
But i need now to know how to insert or update a row, p.e "new client". Can everybody recommends me a book or tutorial?
I have an sqlconnection on a clientdatamodule on the clientside apart from clientclassesunit. I was prooving wuth an SQLQuery with an insert SQL Statement but it doen't function.
On the other han i have on the server side:
procedure TServerMethods1.nuevocheque(idcliente,numero,cuenta,idbanco : integer; fr,fc, titular:string ;importe:Double;cobrado:Boolean);
var
ucheque:integer;
begin
with qicheque do
begin
Open;
ParamByName('idcliente').AsInteger:=idcliente;
ParamByName('numero').AsInteger:=numero;
ParamByName('fr').AsDate:=StrToDate(fr);
ParamByName('fc').AsDate:=StrToDate(fc);
ParamByName('importe').AsFloat:=importe;
ParamByName('titular').AsString:=titular;
ParamByName('cobrado').AsBoolean:=cobrado;
ParamByName('cuenta').AsInteger:=cuenta;
ExecSQL();
end;
end;
With this method i try to insert, the statement is into SQL property of the component.
On the client side, i have a TSQLServerMethod wich calls "nuevocheque":
procedure TForm4.BGuardarClick(Sender: TObject);
var
idcliente,numero,cuenta,idbanco:integer;
titular:string;
cobrado:Boolean;
fr,fc:string;
importe:Double;
begin
ClientModule1.nuevocheque.Create(nil);
with ClientModule1.nuevocheque do
begin
idcliente:=1;
numero:=StrToInt(ENumero.Text);
cuenta:=StrToInt(Ecuenta.Text);
idbanco:=1;
titular:=ENombre.Text;
cobrado:=False;
importe:=StrToFloat(EMonto.Text);
fr:=EFechaEmision.Text;
fc:=EFechacobro.Text;
end;
end;
But it doesn´t function.
Thank for your help
Well, i achieve inserting data into mysql database i had desgined.
This is te code in delphi into a button:
procedure TForm4.BGuardarClick(Sender: TObject);
var
idcliente,numero,cuenta,idbanco:integer;
titular:string;
cobrado:Boolean;
fr,fc:string;
importe:Double;
a:TServerMethods1Client;
interes:Double;
begin
a:=TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
begin
idcliente:=Unit3.id;
numero:=StrToInt(ENumero.Text);
cuenta:=StrToInt(Ecuenta.Text);
idbanco:=lcbbanco.KeyValue;
titular:=ENombre.Text;
cobrado:=False;
if (EP.Text<>'') then
begin
importe:=StrToFloat(EHC.Text);
end
else
begin
importe:=StrToFloat(EMonto.Text);
end;
fr:=EFechaEmision.Text;
fc:=EFechacobro.Text;
end;
a.nuevocheque(idcliente,numero,cuenta, idbanco,fr,fc,titular,importe,cobrado);
end;
I've called to method create() with the SQL component such as M Diwo said me.
Im too hapy. Thanks to all
I don't know what you use as database connection, for my own convenience I have slightly modified for dbGO (parameters passed by variant).
Also I have made a function from the server method, like this the client can be notified that there has been a problem (with the query, connection,...). Here is the server method:
//server
function TServerMethods1.NuevoCheque(idcliente, numero, cuenta,
idbanco: integer; fr, fc, titular: string; importe: Double;
cobrado: Boolean): Boolean;
begin
try
with qicheque, Parameters do
begin
Close;
ParamByName('idcliente').Value:=idcliente;
ParamByName('numero').Value:=numero;
ParamByName('fr').Value:=StrToDate(fr);
ParamByName('fc').Value:=StrToDate(fc);
ParamByName('importe').Value:=importe;
ParamByName('titular').Value:=titular;
ParamByName('cobrado').Value:=cobrado;
ParamByName('cuenta').Value:=cuenta;
ExecSQL();
end;
Result := true;
except
Result := false;
//raise; <-- uncomment if you want to handle this properly in your code
end;
end;
For the client I suppose you generated a proxy unit that generally creates an object called ServerMethods1 ?
You must pass the client dbx connection to this - I say this because I saw you put nil in your code.
// client
procedure TfrmClient.BGuardaClick(Sender: TObject);
var
sm : TServerMethods1Client; // <-- generated by proxy generator
idcliente,numero,cuenta,idbanco : integer;
fr,fc, titular : string ;
importe : Double;
cobrado : Boolean;
begin
sm := TServerMethods1Client.Create(SQL.DBXConnection);
if sm.nuevocheque(idcliente,numero,cuenta,idbanco, fr,fc, titular, importe, cobrado) then
// ok
else
// error
sm.Free;
end;
hth
You can use calls to remote methods, but they won't automatically update your data aware controls automatically. Datasnap is able to handle it. First, you need to add/update/remove data on the client. It happens in the local cache managed by the TClientDataset, even when you "Post".
When you're ready, you need to "apply" changes to the remote server calling the Apply() method.
When you call it, the provider component on the server receives a "delta" with the record to change from the client dataset, and will automatically generate the needed INSERT/UPDATED/DELETE SQL statements.
If you don't like them, or you need to perform more complex processing, you can use the provider events to perform the needed operations yourself for each changed record and then tell the provider you did it to avoid the automatic processing. Then the provider passes back the "delta" to the client, where it is used to updated the data aware controls. You can also modify the "delta" before it is passed back.
Read in the documentation the explanation of the Datasnap architecture - it's a multistep design where several components work to allow for a multi-tier implementation.

Fault in Ole Automation Server

I set up an OLE Automation Server and an OLE Client in Delphi XE2. My two Methods are the following:
function TMyCom2.Get_Text() : IStrings;
begin
GetOleStrings(unit1.Form1.Memo1.Lines, Result);
end;
and:
procedure TMyCom2.Set_Text(const value: IStrings);
begin
SetOleStrings(unit1.Form1.Memo1.Lines, value);
end;
Now I tried to call both by the client.
The second Method(Set_Text) was working perfectly fine.
But the first one(Get_Text) wich should gather the content of the memo of the server and write it into the memo of the client, caused this exception:
Exception error of the server!
To get the Ole information I wrote this on the client side:
procedure TForm1.Button1Click(Sender: TObject);
var
aStrings : IStrings;
begin
aStrings.Add(Server.Get_Text);
SetOleStrings(Memo1.Lines, aStrings);
end;
I have no idea what could be wrong and I was incredibly grateful if somebody could take a look at this code and tell me what the hell is going wrong;D
PS: I already tested it with Integers and it worked so the Problem has to do something with Strings
I uploaded the two projects on Dropbox so feel free to download them:
Client: here
Server: here

Preventing multiple instances - but also handle the command line parameters?

I am handling from my Application associated extension files from Windows. So when you double click a file from Windows it will execute my program, and I handle the file from there, something like:
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 0 to ParamCount -1 do
begin
if SameText(ExtractFileExt(ParamStr(i)), '.ext1') then
begin
// handle my file..
// break if needed
end else
if SameText(ExtractFileExt(ParamStr(i)), '.ext2') then
begin
// handle my file..
// break if needed
end else
end;
end;
That works pretty much how I want it to, but when I was testing I realised it does not consider using only one instance of my program.
So for example, if I selected several Files from Windows and opened them all at the same time, this will create the same number of instances of my program with the number of Files being opened.
What would be a good way to approach this, so that instead of several instances of my program being opened, any additional Files from Windows being opened will simply focus back to the one and only instance, and I handle the Files as normal?
Thanks
UPDATE
I found a good article here: http://www.delphidabbler.com/articles?article=13&part=2 which I think is what I need, and shows how to work with the Windows API as mentioned by rhooligan. I am going to read through it now..
Here is some simple example code that gets the job done. I hope it is self-explanatory.
program StartupProject;
uses
SysUtils,
Messages,
Windows,
Forms,
uMainForm in 'uMainForm.pas' {MainForm};
{$R *.res}
procedure Main;
var
i: Integer;
Arg: string;
Window: HWND;
CopyDataStruct: TCopyDataStruct;
begin
Window := FindWindow(SWindowClassName, nil);
if Window=0 then begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end else begin
FillChar(CopyDataStruct, Sizeof(CopyDataStruct), 0);
for i := 1 to ParamCount do begin
Arg := ParamStr(i);
CopyDataStruct.cbData := (Length(Arg)+1)*SizeOf(Char);
CopyDataStruct.lpData := PChar(Arg);
SendMessage(Window, WM_COPYDATA, 0, NativeInt(#CopyDataStruct));
end;
SetForegroundWindow(Window);
end;
end;
begin
Main;
end.
unit uMainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TMainForm = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
public
procedure ProcessArgument(const Arg: string);
end;
var
MainForm: TMainForm;
const
SWindowClassName = 'VeryUniqueNameToAvoidUnexpectedCollisions';
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WinClassName := SWindowClassName;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to ParamCount do begin
ProcessArgument(ParamStr(i));
end;
end;
procedure TMainForm.ProcessArgument(const Arg: string);
begin
ListBox1.Items.Add(Arg);
end;
procedure TMainForm.WMCopyData(var Message: TWMCopyData);
var
Arg: string;
begin
SetString(Arg, PChar(Message.CopyDataStruct.lpData), (Message.CopyDataStruct.cbData div SizeOf(Char))-1);
ProcessArgument(Arg);
Application.Restore;
Application.BringToFront;
end;
end.
The logic goes something like this. When you start your application, you iterate through the list of running processes and see if your application is already running. If it is running, you need to activate the window of that instance and then exit.
Everything you need to do this is in the Windows API. I found this sample code on CodeProject.com that deals with processes:
http://www.codeproject.com/KB/system/Win32Process.aspx
On finding and activating a window, the basic approach is to find the window of interest using the window class name then activate it.
http://www.vb6.us/tutorials/activate-window-api
Hopefully this gives you a good starting point.
There are many answers here that show how to implement this. I want to show why NOT to use the FindWindow approach.
I am using FindWindow (something similar with the one shown by David H) and I have seen it failed starting with Win10 - I don't know what they changed in Win10.
I think the gap between the time when the app starts and the time when we set the unique ID via CreateParams is too big so another instance has somehow time to run in this gap/interval.
Imagine two instances started at only 1ms distance (let's say that the user click the EXE file and then presses enter and keeps it pressed by accident for a short while). Both instances will check to see if a window with that unique ID exists, but none of them had the chance to set the flag/unique ID because creating the form is slow and the unique ID is set only when the form is constructed. So, both instances will run.
So, I would recommend the CreateSemaphore solution instead:
https://stackoverflow.com/a/460480/46207
Marjan V already proposed this solution but didn't explained why it is better/safer.
I'd use mutexes. You create one when your program starts.
When the creation fails it means another instance is already running. You then send this instance a message with your command line parameters and close. When your app receives a message with a command line, it can parse the parameters like you are already doing, check to see whether it already has the file(s) open and proceed accordingly.
Processing this app specific message ia also the place to get your app to the front if it isn't already. Please do this politely (SetForegroundWindow) without trying to force your app in front of all others.
function CreateMutexes(const MutexName: String): boolean;
// Creates the two mutexes to see if the program is already running.
// One of the mutexes is created in the global name space (which makes it
// possible to access the mutex across user sessions in Windows XP); the other
// is created in the session name space (because versions of Windows NT prior
// to 4.0 TSE don't have a global name space and don't support the 'Global\'
// prefix).
var
SecurityDesc: TSecurityDescriptor;
SecurityAttr: TSecurityAttributes;
begin
// By default on Windows NT, created mutexes are accessible only by the user
// running the process. We need our mutexes to be accessible to all users, so
// that the mutex detection can work across user sessions in Windows XP. To
// do this we use a security descriptor with a null DACL.
InitializeSecurityDescriptor(#SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(#SecurityDesc, True, nil, False);
SecurityAttr.nLength := SizeOf(SecurityAttr);
SecurityAttr.lpSecurityDescriptor := #SecurityDesc;
SecurityAttr.bInheritHandle := False;
if (CreateMutex(#SecurityAttr, False, PChar(MutexName)) <> 0 )
and (CreateMutex(#SecurityAttr, False, PChar('Global\' + MutexName)) <> 0 ) then
Result := True
else
Result := False;
end;
initialization
if not CreateMutexes('MyAppNameIsRunningMutex') then
//Find and SendMessage to running instance
;
end.
Note: above code is adapted from an example on the InnoSetup site. InnoSetup creates installer applications and uses this approach in the installer to check whether (a previous version of) the application being installed is already running.
Finding the other instance and sending it a message, I'll leave for another question (or you can use the WM_COPYDATA approach from David's answer). Actually, there is a StackOverflow question that deals exactly with this: How to get the process thread that owns a mutex Getting the process/thread that owns the mutex may be a bit of a challenge, but the answers to this question do address ways to get the information from one instance to the other.
Windows has different ways to handle file associations to executable.
The "command line" approach is only the simplest one, but also the most limited one.
It also supports DDE (it still works although officially deprecated) and COM (see http://msdn.microsoft.com/en-us/library/windows/desktop/cc144171(v=vs.85).aspx).
If I recall correctly both DDE and COM will let your application receive the whole list of selected files.
I used window/message approach by myself with addition of events for tracking if the other instance is running:
Try to create event "Global\MyAppCode" (the "Global" namespace is used for handling various user sessions as I needed single instance system-wide; in your case you'll probably prefer "Local" namespace which is set by default)
If CreateEvent returned error and GetLastError = ERROR_ALREADY_EXISTS then the instance is running already.
FindWindow/WM_COPYDATA to transfer data to that instance.
But the drawbacks with messages/windows are more than significant:
You must always keep your window's Caption constant. Otherwise you'll have to list all the windows in the system and loop through them for partial occurrence of some constant part. Moreover the window's caption could be easily changed by a user or 3rd part app so the search would fail.
Method requires a window to be created so no console/service apps, or they must create a window and perform message loop especially for handling the single instance.
I'm not sure FindWindow could find a window that is opened in another user session
For me, WM_COPYDATA is rather awkward method.
So currently I'm a fan of named pipe approach (haven't implemented it yet though).
On launch, app tries to connect to "Global\MyAppPipe". If successed, other instance is running. If failed, it creates this pipe and finishes instance check.
2nd instance writes the required data to pipe and exits.
1st instance receives data and does some stuff.
It works through all user sessions (with namespace "Global") or just a current session; it doesn't depend on strings used by UI (no localization and modification issues); it works with console and service apps (you'll need to implement pipe reading in a separate thread/message loop though).

Resources