Windows Services in Delphi with database connection - database

I would just like a tip about a situation.
I created a Windows service that does the task management of my application.
The service connect to the database (Firebird) and call a component that does the task management.
The process works fine, however, in Windows 10 the service does not start automatically after the computer is restarted. In other versions of Windows everything works perfectly. In testing, I have identified that if I comment on the method that calls the execution of the tasks, the service usually starts on Windows 10.
Procedure TDmTaskService.ServiceExecute(Sender: TService);
Begin
Inherited;
While Not Terminated Do
Begin
//Process;
Sleep(3000);
ServiceThread.ProcessRequests(False);
End;
End;
The problem is that nothing exception is generated in component or service.
By analyzing the Windows Event Monitor, I have identified that the error that occurred with my service is Timeout, in which case the service was unable to connect to the service manager within the time limit. No more exceptions are generated.
Would anyone have any about Windows Services made in Delphi that connect to database?
Example of my source code:
**Base class:**
unit UnTaskServiceDmBase;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;
type
TDmTaskServicosBase = class(TService)
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
DmTaskServiceBase: TDmTaskServicosBase;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
DmJBServicosBase.Controller(CtrlCode);
end;
function TDmTaskServicosBase.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
end.
**Service Class:**
Unit UnTaskServiceDm;
Interface
Uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
UnJBTask,
UnJBReturnTypes,
UnJBUtilsFilesLog,
UnTaskServiceDmConfig,
UnTaskServiceDmConnection,
ExtCtrls,
IniFiles;
Type
TDmTaskService = Class(TDmTaskServicosBase)
Procedure ServiceExecute(Sender: TService);
Procedure ServiceCreate(Sender: TObject);
Procedure ServiceStop(Sender: TService; Var Stopped: Boolean);
Private
FTaskServiceConfig: TDmTaskServiceConfig;
FStatus: TResultStatus;
FDmConnection: TDmTaskServiceConnection;
FJBTask: TJBTask;
FLog: TJBUtilsFilesLog;
Procedure ExecuteTasksSchedule;
Procedure UpdateServiceInformation;
Procedure Process;
Procedure UpdateConnection;
Public
Function GetServiceController: TServiceController; Override;
End;
Implementation
{$R *.DFM}
Procedure ServiceController(CtrlCode: DWord); Stdcall;
Begin
DmTaskService.Controller(CtrlCode);
End;
Procedure TDmTaskService.UpdateConnection;
Begin
Try
FDmConnection.SqcCon.Connected := False;
FDmConnection.SqcCon.Connected := True;
FLog.Adicionar('Conexão com banco restabelecida.');
FLog.FinalizarLog;
Except
On E: Exception Do
Begin
FLog.Adicionar('Erro ao restabelecer conexão com o banco de dados.' +
sLineBreak + sLineBreak + E.Message);
FLog.FinalizarLog;
End;
End;
End;
Procedure TDmTaskService.UpdateServiceInformation;
Begin
Inherited;
Try
Try
FTaskServiceConfig.Load;
FLog.Adicionar('Dados registro serviço.');
FLog.Adicionar('Nome: ' + FTaskServiceConfig.ServiceName);
FLog.Adicionar('Descrição: ' + FTaskServiceConfig.ServiceDescription);
If (FTaskServiceConfig.ServiceName <> EmptyStr) And
(FTaskServiceConfig.ServiceDescription <> EmptyStr) Then
Begin
Name := FTaskServiceConfig.ServiceName ;
DisplayName := FTaskServiceConfig.ServiceDescription;
End;
FTaskServiceConfig.Close;
Except
On E: Exception Do
Begin
FLog.Adicionar('Erro adicionar dados registro serviço.');
FLog.Adicionar('Erro ocorrido: ' + sLineBreak + sLineBreak + E.Message);
End;
End;
Finally
FLog.Adicionar('Name: ' + Name);
FLog.Adicionar('DisplayName: ' + DisplayName);
FLog.FinalizarLog;
End;
End;
Procedure TDmTaskService.Process;
Begin
Try
If FDmConnection.SqcCon.Connected Then
Begin
ExecuteTasksSchedule;
End
Else
UpdateConnection;
Except
On E: Exception Do
Begin
FLog.Adicionar('Ocorreu um erro ao checar as tarefas.' + sLineBreak +
'Erro ocorrido: ' + sLineBreak + E.Message);
FLog.FinalizarLog;
UpdateConnection;
End;
End;
End;
Procedure TDmTaskService.ExecutarTarefasAgendadas;
Begin
If FJBTask.ExistTaskDelayed Then
Begin
Try
FJBTask.ExecuteTasks;
Except
On E: Exception Do
Begin
FLog.Adicionar('Ocorreu um erro ao executar as tarefas agendadas.' +
sLineBreak + 'Erro ocorrido: ' + sLineBreak + E.Message);
FLog.FinalizarLog;
UpdateConnection;
End;
End;
End;
End;
Function TDmTaskService.GetServiceController: TServiceController;
Begin
Result := ServiceController;
End;
Procedure TDmTaskService.ServiceCreate(Sender: TObject);
Begin
Inherited;
Try
FLog := TJBUtilsFilesLog.Create;
FLog.ArquivoLog := IncludeTrailingPathDelimiter(FLog.LogFolder) + 'TaksService.log';
FDmConnection := TDmTaskServiceConexao.Create(Self);
FDmConnection.Log := FLog;
FJBTask := TJBTarefa.Create(Self);
FJBTask.SQLConnection := FDmConnection.SqcConexao;
FTaskServiceConfig := TDmTaskServiceConfig.Create(Self);
FTaskServiceConfig.SQLConnection := FDmConnection.SqcConexao;
FStatus := FDmConnection.ConfigurouConexao;
If FStatus.ResultValue Then
Begin
UpdateServiceInformation;
End
Else
Begin
FLog.Adicionar(FStatus.MessageOut);
FLog.FinalizarLog;
End;
Except
On E: Exception Do
Begin
FLog.Adicionar('Não foi possível iniciar o serviço.' + sLineBreak +
'Erro ocorrido: ' + sLineBreak + sLineBreak + E.Message);
FLog.FinalizarLog;
Abort;
End;
End;
End;
Procedure TDmTaskService.ServiceExecute(Sender: TService);
Begin
Inherited;
While Not Terminated Do
Begin
Process;
Sleep(3000);
ServiceThread.ProcessRequests(False);
End;
End;
Procedure TDmTaskService.ServiceStop(Sender: TService; Var Stopped: Boolean);
Begin
Inherited;
If Assigned(FDmConnection) Then
Begin
FLog.Adicionar('Finalizando serviço.');
FLog.Adicionar('Fechando conexão.');
Try
FDmConnection.SqcConexao.Close;
Finally
FLog.FinalizarLog;
End;
End;
End;
End.

By analyzing the Windows Event Monitor, I have identified that the error that occurred with my service is Timeout, in which case the service was unable to connect to the service manager within the time limit. No more exceptions are generated.
Do not connect to your database, or do any other lengthy operations, in the TService.OnCreate event. Such logic belongs in the TService.OnStart event instead. Or better, create a worker thread for it, and then start that thread in the TService.OnStart event and terminate it in the TService.On(Stop|Shutdown) events.
When the SCM starts your service process, it waits for only a short period of time for the new process to call StartServiceCtrlDispatcher(), which connects the process to the SCM so it can start receiving service requests. StartServiceCtrlDispatcher() is called by TServiceApplication.Run() after all TService objects have been fully constructed first. Since the OnCreate event is called while your process is trying to initialize itself, before StartServiceCtrlDispatcher() is called, any delay in service construction can cause the SCM to timeout and kill the process.
Also, you should get rid of your TService.OnExecute event handler completely. You shouldn't even be using that event at all, and what you currently have in it is no better than what TService already does internally when OnExecute is not assigned any handler.

In your service code :
- you can try to add Dependencies on your Firebird Service
- you can increase WaitHint
if it still not work : you can start as automatic but "Delayed"

I found it otherwise to solve, however, I thank everyone for the tips, because in a timely manner you will make improvements to my service.
The solution was to extend the service startup timeout through the Windows ServicesPipeTimeout registry key.
For my case it worked perfectly.
I increased the value of ServicesPipeTimeout to 120000 (2 minutes).
By default the value is 30000 (30 seconds) or less.
To manual Edit:
1) Open the Windows Regedit App;
2) Locate and then click the following registry subkey:
  - HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control
In the panel values, locate the ServicesPipeTimeout entry.
** Note **:
If the ServicesPipeTimeout entry does not exist, you must create it. To do
this, follow these steps:
 - On the Edit menu, point to New, and then click DWORD Value.
 - Type ServicesPipeTimeout, and then press ENTER.
3) Right-click ServicesPipeTimeout, and then click Modify.
4) Click Decimal, type 120000, and then click OK.
** 120000 miliseconds = 2 minutes
5) Restart the computer.
In Delphi (Sample registry value):
Procedure TForm3.JBButton3Click(Sender: TObject);
Const
CKeyConfigTimeout = 'SYSTEM\CurrentControlSet\Control';
CValueConfigTimeout = 'ServicesPipeTimeout';
Var
LReg: TRegistry;
Begin
LReg := TRegistry.Create;
Try
LReg.RootKey := HKEY_LOCAL_MACHINE;
LReg.OpenKey(CKeyConfigTimeout, False);
LReg.WriteInteger(CValueConfigTimeout, 120000);
Finally
LReg.CloseKey;
FreeAndNil(LReg);
End;
End;
Note: The delphi application with the registry update code needs to run in administrator mode for Windows Vista / Server or Superior versions;

Related

SQL Server Stored Procedure is not raising error in my program

CREATE PROCEDURE [dbo].[spTest]
#Pozitii varchar(max),
#NrZile int
AS
set #Pozitii = SUBSTRING(#Pozitii,0,LEn(#Pozitii))
CREATE TABLE #Pozitii (part varchar(20) null)
INSERT INTO #Pozitii(part)
SELECT part
FROM dbo.SDF_SplitString(#Pozitii,',')
if exists (SELECT * FROM #Pozitii)
RAISERROR('asdf',16,-1)
else RAISERROR('else',16,-1)
So runing this SP in SQL like this
exec [spTest] '11,12,13,',1
Returns:
(3 row(s) affected)
Msg 50000, Level 16, State 1, Procedure spTest, Line 27
asdf
Now if I run my procedure in delphi ( using an ADO object)
procedure TframePlanificatorPozitieComanda.Button5Click(Sender: TObject);
begin
try
with dm.spTest do
begin
Close;
Parameters.ParambyName('#Pozitii').Value := '11,12,13,';
Parameters.ParambyName('#NrZile').Value := 1;
ExecProc;
end;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
This code is not raising any errors?Any ideas why?
Have you tried adding:
SET NOCOUNT ON;
to your stored procedure? I think the exception is in a second resultset and is getting hidden by the first select result count.
The error is raising in sql-server.. but in delphi is not .. because there the procedure is already executed..
But in Delphi you can check if the procedure is run successfully or not regards what the result of the procedure (some of procedures have no output parameters).
In delphi please check your adostoredprocedure.parameters[0] like:
showmessage(vartostr(self.ADOStoredProc1.Parameters[0].Value))
.
if the result <> 0 that means error.
Using Sql Server 2014, I don't get the behaviour you describe.
I have a stored proc on the server defined as
CREATE PROCEDURE spRaiseError(#AnError int)
AS
BEGIN
declare #Msg Char(20)
if #AnError > 0
begin
Select #Msg = 'MyError ' + convert(Char(8), #AnError)
RaisError(#Msg, 16, -1)
end
else
select 1
END
I have a minimalist D7 Ado project with a TAdoConnection, TAdoQuery, TDataSource and TDBGrid connected up as you'd expect, a TEdit and a TButton.
Using this code
procedure TForm1.Button1Click(Sender: TObject);
var
S : String;
ErrorCount : Integer;
begin
S := 'exec spRaiseError ' + Edit1.Text;
AdoQuery1.SQL.Text := S;
try
AdoQuery1.Open;
except
end;
ErrorCount := AdoConnection1.Errors.Count;
Caption := IntToStr(ErrorCount);
end;
, if the number in Edit1 is > 0 I get only the error number on the form's caption, whereas if it contains 0 I see the value 1 in the DBGrid.
Btw, with "Stop on language exceptions" checked in the D7 debugger settings, without the try/except around AdoQuery1.Open, the debugger sees and catches the exception from the server.
Anyway, the take-home message from this answer is that you can use the TAdoConnection's Errors collection to detect whether there was an error and, if there was, you can get more information from it. See the Delphi OLH amd MS Ado documentation for more info about the Errors collection of TAdoConnection and other Ado-based Delphi classes.

Delphi: What's wrong with REST API based on mORMot?

I try to build REST Web Server based on mORMot framework (Delphi) and have issue with getting data from remote DB.
Server.dpr
const
CONN_STR =
'DRIVER=SQL Server Native Client 10.0;UID=user;PWD=pass;server=server;'
+ 'Trusted_Connection=No;MARS_Connection=yes';
var
Model: TSQLModel;
RESTServer: TSQLRestServerDB;
Application: TwoStreamgateApplication;
HTTPServer: TSQLHttpServer;
ODBCConnProp: TSQLDBConnectionPropertiesThreadSafe;
begin
SQLite3Log.Family.Level := LOG_VERBOSE;
SQLite3Log.Family.PerThreadLog := ptIdentifiedInOnFile;
ODBCConnProp := TODBCConnectionProperties.Create('', CONN_STR, 'wo', 'wo');
try
ODBCConnProp.ThreadingMode := tmMainConnection;
Model := CreateModel;
VirtualTableExternalRegisterAll(Model, ODBCConnProp);
try
RESTServer := TSQLRestServerDB.Create(Model, ':memory:');
try
RESTServer.DB.Synchronous := smNormal;
RESTServer.DB.LockingMode := lmExclusive;
RESTServer.CreateMissingTables;
Application := TwoStreamgateApplication.Create;
try
Application.Start(RESTServer);
HTTPServer := TSQLHttpServer.Create('8092', [RESTServer]
{$IFNDEF ONLYUSEHTTPSOCKET}, '+', useHttpApiRegisteringURI{$ENDIF});
try
HTTPServer.RootRedirectToURI('api/default');
RESTServer.RootRedirectGet := 'api/default';
writeln('"MVC WOStreamgate Server" launched on port 8092 using ',
HTTPServer.HttpServer.ClassName);
writeln(#10'You can check http://localhost:8092/api/info for information');
writeln('or point to http://localhost:8092 to access the web app.');
writeln(#10'Press [Enter] to close the server.'#10);
readln;
writeln('HTTP server shutdown...');
finally
HTTPServer.Free;
end;
finally
Application.Free;
end;
finally
RESTServer.Free;
end;
finally
Model.Free;
end;
finally
ODBCConnProp.Free;
end;
Model is described below in ServerModel.pas unit
type
TSQLBaseData = class(TSQLRecord)
private
FName: RawUTF8;
FDetailURL: RawUTF8;
FLogoURL: RawUTF8;
FDescription: RawUTF8;
published
property Name: RawUTF8 read FName write FName;
property Description: RawUTF8 read FDescription write FDescription;
property LogoURL: RawUTF8 read FLogoURL write FLogoURL;
property DetailURL: RawUTF8 read FDetailURL write FDetailURL;
end;
TSQLStation = class(TSQLBaseData)
end;
function CreateModel: TSQLModel;
implementation
function CreateModel: TSQLModel;
begin
result := TSQLModel.Create(
[TSQLStation],
'api');
end;
View model in ServerViewModel.pas unit
TServerApplication = class(TMVCApplication, IServerApplication)
public
procedure Start(aServer: TSQLRestServer); reintroduce;
procedure StationView(ID: TID; out Station: TSQLStation);
end;
implementation
procedure TServerApplication.Start(aServer: TSQLRestServer);
begin
inherited Start(aServer,TypeInfo(IServerApplication));
fMainRunner := TMVCRunOnRestServer.Create(Self).
SetCache('Default',cacheRootIfNoSession,15);
aServer.Cache.SetCache(TSQLStation);
end;
procedure TServerApplication.StationView(ID: TID; out Station: TSQLStation);
begin
RestModel.Retrieve(ID, Station);
end;
and now once I type http://localhost:8092/api/station/10 in browser, I get response
{ "errorCode":404, "errorText":"Not Found" }
in case I type http://localhost:8092/api/stationview/10 in browser I get rendered page, but without info about station=10 or any other.
Where did I make a mistake? I want simply get JSON-response to my request
UPDATE:
Another info that can help. Once I try the query
procedure TestQuery();
var
SQLDBConnection: TODBCConnection;
Q: TQuery;
begin
SQLDBConnection := TODBCConnection.Create(ODBCConnProp);
Q := TQuery.Create(SQLDBConnection);
try
Q.SQL.Clear; // optional
Q.SQL.Add('select * from dbo.Station');
Q.Open;
...
I catch an error:
Project Server.exe raised exception class EODBCException with message 'TODBCStatement - TODBCLib
error: [HY106] [Microsoft][SQL Server Native Client 10.0]Fetch type
out of range (0) '.
Solution is very simple - need to add:
Database=myDataBase;
to
CONN_STR = 'DRIVER=SQL Server Native Client 10.0;UID=user;PWD=pass;server=server;'
+ 'Trusted_Connection=No;MARS_Connection=yes';Database=myDataBase;
Thanks Arnaud for great framework!!!

Unable to insert Unicode into SQL Server 2008 using Delphi ZeosLib and Delphi 7

I'm having trouble inserting Unicode into a SQL Server database using Delphi ZeosLib and Delphi 7, and then reading the inserted value. I've created a simple test program that first inserts and then queries the inserted value.
The test table schema:
CREATE TABLE [dbo].[incominglog](
[message] [nvarchar](500) NULL
) ON [PRIMARY]
I've upload the simple test program source (ZeosLib source included) - click here to download. I've also included ntwdblib.dll but you can use your own.
The test program also requires TNT component which can be downloaded from here
Using the test program, the Unicode characters that I have inserted appear as question marks on retrieval - I'm not certain whether the problem lies with the insert code or the query code.
I've also tried encoding the data into utf-8 before inserting and then decoding the data after retrieving from utf-8 - please search "//inserted as utf8" in the test program source. I'm able to view the Unicode after it has been decoded, so this method works. However, for my actual application, I can't encode as UTF-8 as SQL Server doesn't support UTF-8 completely - some characters can't be stored. See my previous question here.
Will appreciate any pointers. :)
Meanwhile, here's the source for the Test program:
unit Unit1;
interface
uses
ZConnection, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ZAbstractRODataset, ZAbstractDataset, ZAbstractTable, ZDataset,
StdCtrls, TntStdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
TntMemo1: TTntMemo;
Button2: TButton;
TntEdit1: TTntEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
FZConnection: TZConnection;
FZQuery: TZQuery;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
tntmemo1.Lines.Clear;
FZConnection := TZConnection.Create(Owner);
FZConnection.LoginPrompt := False;
FZQuery := TZQuery.Create(Owner);
FZQuery.Connection := FZConnection;
FZConnection.Protocol := 'mssql';
FZConnection.Database := 'replace-with-your-db';
FZConnection.HostName := 'localhost';
FZConnection.User := 'sa';
FZConnection.Password := 'replace-with-your-password';
FZConnection.Connect;
FZQuery.SQL.Text := 'SELECT * from incominglog';
FZQuery.ExecSQL;
FZQuery.Open;
FZQuery.First;
while not FZQuery.EOF do
begin
tntmemo1.Lines.add(FZQuery.FieldByName('message').AsString);
// tntmemo1.Lines.add(utf8decode(FZQuery.FieldByName('message').AsString)); //inserted as utf8
FZQuery.Next;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
sqlstring, data:widestring;
begin
FZConnection := TZConnection.Create(Owner);
FZConnection.LoginPrompt := False;
FZQuery := TZQuery.Create(Owner);
FZQuery.Connection := FZConnection;
FZConnection.Protocol := 'mssql';
FZConnection.Database := 'replace-with-your-db';
FZConnection.HostName := 'localhost';
FZConnection.User := 'sa';
FZConnection.Password := 'replace-with-your-password';
FZConnection.Connect;
data:= tntedit1.Text;
// data:= utf8encode(tntedit1.Text); //inserted as utf8
sqlstring:= 'INSERT INTO INCOMINGLOG ([MESSAGE]) VALUES(N''' + data + ''')';
FZQuery.SQL.Text := sqlstring;
FZQuery.ExecSQL;
end;
end.
I have not tested your example, but I'm able to save and retrieve from database Unicode characters without problem on SQL Server with Delphi 7 VCL/CLX and zeoslib.
I think in your case it will be enough changing your save procedure like this :
procedure TForm1.Button2Click(Sender: TObject);
var
sqlstring : widestring;
data : UTF8String;
begin
FZConnection := TZConnection.Create(Owner);
FZConnection.LoginPrompt := False;
FZQuery := TZQuery.Create(Owner);
FZQuery.Connection := FZConnection;
FZConnection.Protocol := 'mssql';
FZConnection.Database := 'replace-with-your-db';
FZConnection.HostName := 'localhost';
FZConnection.User := 'sa';
FZConnection.Password := 'replace-with-your-password';
FZConnection.Connect;
data:= UTF8String( utf8encode(tntedit1.Text) );
sqlstring:= 'INSERT INTO INCOMINGLOG ([MESSAGE]) VALUES(:DATA)';
FZQuery.SQL.Text := sqlstring;
FZQuery.ParamByName('DATA').AsString := data;
FZQuery.ExecSQL;
end;
The point is changing your data string variable in UTF8String type, and use parameters pass the data string to the query ...
To be honest I use it with a ZTable and Post command, but it should be the same with a ZQuery like yours ...

How to import an Excel file into Access database, using delphi

Im writing a database application, using Delphi and need to import data on a excel sheet and save it in a access database. I have no idea how to do this, what components to use, or if it is even possible, can you please help me.
You have several options, try one of these
1) using the DoCmd.TransferSpreadsheet function , this method is simpler but not very flexible.
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils,
ActiveX,
ComObj;
procedure ImportDataAccess(const AccessDb, TableName, ExcelFileName:String);
Const
acQuitSaveAll = $00000001;
acImport = $00000000;
acSpreadsheetTypeExcel9 = $00000008;
acSpreadsheetTypeExcel12 = $00000009;
var
LAccess : OleVariant;
begin
//create the COM Object
LAccess := CreateOleObject('Access.Application');
//open the access database
LAccess.OpenCurrentDatabase(AccessDb);//if the access database doesn't exist use the NewCurrentDatabase method instead.
//import the data
LAccess.DoCmd.TransferSpreadsheet( acImport, acSpreadsheetTypeExcel9, TableName, ExcelFileName, True);
LAccess.CloseCurrentDatabase;
LAccess.Quit(1);
end;
begin
try
CoInitialize(nil);
try
ImportDataAccess('C:\Data\Database1.accdb','Sales','C:\Data\Sales.xlsx');
Writeln('Done');
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
2) using the ado components, a more flexible way.
{$APPTYPE CONSOLE}
{$R *.res}
uses
Data.DB,
Data.Win.ADODB,
SysUtils,
ActiveX,
ComObj;
procedure ImportDataADO(const AccessDb, TableName, ExcelFileName:String);
var
LAdoQueryExcel : TADOQuery;
LADOAccesCmd : TADOCommand;
begin
LAdoQueryExcel:=TADOQuery.Create(nil);
LADOAccesCmd:=TADOCommand.Create(nil);
try
//set the connection string for access
LADOAccesCmd.ConnectionString:=Format('Provider=Microsoft.ACE.OLEDB.12.0;Data Source=%s;',[AccessDb]);
LADOAccesCmd.Parameters.Clear;
LADOAccesCmd.CommandText:='INSERT INTO Sales (id,name) VALUES (:id,:name)';
LADOAccesCmd.ParamCheck:=False;
//set the connection string for excel
LAdoQueryExcel.ConnectionString:=Format('Provider=Microsoft.ACE.OLEDB.12.0;Data Source=%s;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1"',[ExcelFileName]);
LAdoQueryExcel.SQL.Add('SELECT * FROM [Sheet1$]');
LAdoQueryExcel.Open;
while not LAdoQueryExcel.eof do
begin
LADOAccesCmd.Parameters.ParamByName('id').Value := LAdoQueryExcel.FieldByname('id').AsInteger;
LADOAccesCmd.Parameters.ParamByName('name').Value := LAdoQueryExcel.FieldByname('name').AsString;
LADOAccesCmd.Execute;
LAdoQueryExcel.Next;
end;
finally
LAdoQueryExcel.Free;
LADOAccesCmd.Free;
end;
end;
begin
try
CoInitialize(nil);
try
ImportDataADO('C:\Datos\Database1.accdb','Sales','C:\Datos\Sales.xlsx');
Writeln('Done');
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.

View output of 'print' statements using ADOConnection in Delphi

Some of my MS SQL stored procedures produce messages using the 'print' command. In my Delphi 2007 application, which connects to MS SQL using TADOConnection, how can I view the output of those 'print' commands?
Key requirements:
1) I can't run the query more than once; it might be updating things.
2) I need to see the 'print' results even if datasets are returned.
That was an interesting one...
The OnInfoMessage event from the ADOConnection works but the Devil is in the details!
Main points:
use CursorLocation = clUseServer instead of the default clUseClient.
use Open and not ExecProc with your ADOStoredProc.
use NextRecordset from the current one to get the following, but be sure to check you have one open.
use SET NOCOUNT = ON in your stored procedure.
SQL side: your stored procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FG_TEST]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[FG_TEST]
GO
-- =============================================
-- Author: François
-- Description: test multi ADO with info
-- =============================================
CREATE PROCEDURE FG_TEST
AS
BEGIN
-- SET NOCOUNT ON absolutely NEEDED
SET NOCOUNT ON;
PRINT '*** start ***'
SELECT 'one' as Set1Field1
PRINT '*** done once ***'
SELECT 'two' as Set2Field2
PRINT '*** done again ***'
SELECT 'three' as Set3Field3
PRINT '***finish ***'
END
GO
Delphi side:
Create a new VCL Forms Application.
Put a Memo and a Button in your Form.
Copy the following text, change the Catalog and Data Source and Paste it onto your Form
object ADOConnection1: TADOConnection
ConnectionString =
'Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security In' +
'fo=False;Initial Catalog=xxxYOURxxxDBxxx;Data Source=xxxYOURxxxSERVERxxx'
CursorLocation = clUseServer
LoginPrompt = False
Provider = 'SQLOLEDB.1'
OnInfoMessage = ADOConnection1InfoMessage
Left = 24
Top = 216
end
object ADOStoredProc1: TADOStoredProc
Connection = ADOConnection1
CursorLocation = clUseServer
ProcedureName = 'FG_TEST;1'
Parameters = <>
Left = 24
Top = 264
end
In the OnInfoMessage of the ADOConnection put
Memo1.Lines.Add(Error.Description);
For the ButtonClick, paste this code
procedure TForm1.Button1Click(Sender: TObject);
const
adStateOpen = $00000001; // or defined in ADOInt
var
I: Integer;
ARecordSet: _Recordset;
begin
Memo1.Lines.Add('==========================');
ADOStoredProc1.Open; // not ExecProc !!!!!
ARecordSet := ADOStoredProc1.Recordset;
while Assigned(ARecordSet) do
begin
// do whatever with current RecordSet
while not ADOStoredProc1.Eof do
begin
Memo1.Lines.Add(ADOStoredProc1.Fields[0].FieldName + ': ' + ADOStoredProc1.Fields[0].Value);
ADOStoredProc1.Next;
end;
// switch to subsequent RecordSet if any
ARecordSet := ADOStoredProc1.NextRecordset(I);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
ADOStoredProc1.Close;
end;
In .net's connection classes there is an event called InfoMessage. In a handler for this event you can retrieve the InfoMessage (print statements) from the event args.
I believe Delphi has a similar event called "OnInfoMessage" that would help you.
I dont think that is possible.
You might use a temp table to dump print statements and return it alongwith results.
Some enhancements to Francois' code (as tested with DXE2) to cater for multiple print statements and the results from a variable number of selects. The changes are subtle.
procedure TForm1.ADOConnection1InfoMessage(Connection: TADOConnection;
const Error: Error; var EventStatus: TEventStatus);
var
i: integer;
begin
// show ALL print statements
for i := 0 to AdoConnection1.Errors.Count - 1 do
begin
// was: cxMemo1.Lines.Add(Error.Description);
cxMemo1.Lines.Add(
ADOConnection1.Errors.Item[i].Description);
end;
end;
procedure TForm1.cxButton1Click(Sender: TObject);
const
adStateOpen = $00000001; // or uses ADOInt
var
records: Integer;
ARecordSet: _RecordSet;
begin
cxMemo1.Lines.Add('==========================');
ADOStoredProc1.Open;
try
ARecordSet := ADOStoredProc1.RecordSet; // initial fetch
while Assigned(ARecordSet) do
begin
// assign the recordset to a DataSets recordset to traverse
AdoDataSet1.Recordset := ARecordSet;
// do whatever with current ARecordSet
while not ADODataSet1.eof do
begin
cxMemo1.Lines.Add(ADODataSet1.Fields[0].FieldName +
': ' + ADODataSet1.Fields[0].Value);
AdoDataSet1.Next;
end;
// fetch next recordset if there is one
ARecordSet := ADOStoredProc1.NextRecordSet(records);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
finally
ADOStoredProc1.Close;
end;
end;

Resources