By developing client server applications in Delphi + SQL Server I continuously face the problem to have an exact report of what an action caused on db.
A simple example is:
BEFORE: start "capturing" the DB
user changes a value in an editbox
the user presses a "save" button
AFTER: stop "capturing" the DB
I would like to have a tool that compares the before and after status (I manually capture the BEFORE and AFTER status).
I googled for this kind of tools but all I found are tools for doing data or schema comparison between more datasources.
Thanks.
The following is an extract for an application we have. This code is in a BeforePost event handler that is linked to all of the Dataset components in the application. This linking is done using code as there are a lot of datasets. This doesn't actually log the changes (just lists the fields) but it should be simple enough to change to meet your objective. I don't know if this is exactly right for what you are trying to achieve since you ask for a tool but it would be an effective way of creating a report of all changes
CurrentReport := Format('Table %s modified', [DataSet.Name]);
for i := 0 to DataSet.FieldCount - 1 do
begin
XField := DataSet.Fields[i];
if (XField.FieldKind = fkData) and (XField.Value <> XField.OldValue) then
begin
CurrentReport := CurrentReport + Format(', %s changed', [XField.FieldName])
end;
end;
Note that our code collects a report but only logs it after the post has been successfully completed
Related
I am working on Windows desktop application in Delphi using FireDAC driver and MSSQL database system. Currently, I am having a problem in understanding how multiple sessions (users) should work. Right now, I have three test users, and when I log in with any of them, every session has the same data and functionalities. I don't want that. I want that each user (each session) has different data and functionalities.
Note that this is different from distributed systems, where tasks are distributed by hosts in a network. I am not interested in distributed system. I have a desktop application.
Could someone explain how to achieve this (different users (sessions) = different data and functionalities)?
You've indicated in a comment that what you want is for a number of users o be able to see different data rows in the same table or tables
That's actually quite quite straightforward: you just need to define, for each user (or user type), the criteria which determine which data rows they are supposed to be able to see, then write a Where clause which selects only those rows. It's generally a bad idea to hard-code users's identities in a database and what data they are permitted to see and what operations they are permitted to carry out on the data.
It's hard to give a concrete example without getting into details of what you are wanting to do, but the following simple example might help.
Suppose you have a table of Customers, and one user is suposed to deal with the USA, the second user deals with France and the third with the rest of the world.
In your app, you could have an enumerated type to represent this:
type
TRegion = (rtUSA, rtFR, rtRoW); // RoW = Rest of the World
Then you could write a function to generate the Where clause of a SQL Select statement like this:
function GetRegionWhereClause(const ARegion : TRegion) : String;
begin
Result := ' Where ';
case ARegion of
rtUSA : Result := Result + ' Customer.Country = ''USA''';
rtFR : Result := Result + ' Customer.Country = ''FR''';
rtRoW : Result := Result + ' not Customer.Country in (''USA'', ''FR'')'
end; { case }
end;
You could then call GetRegionWhereClause when you generate the Sql to open the Customers table.
Similarly define for each user type what operations they are permitted to carry out on the data (Update, Insert, Delete). But implementing that would be more a question of selectively enabling and disable the gui functionality in your app to do the tasksin question.
I use Zeoslib components to interact with my SQLite database file. I have several SQLite database files in a folder. So, I want to be able to open any one of them using Zeoslib component. However, it won't let me. It opens the first database successfully but any database file I open after that, I get access violation error. For the life of me, I simply can't figure it out WHY.
Here is how I open database file.
procedure TMainFrm.Open1Click(Sender: TObject);
var currdb:string;
begin
OpenDlg.InitialDir := BaseDir;
if OpenDlg.Execute = true then
begin
currdb := Extractfilename(OpenDlg.FileName);
DataModule1.ZConnection1.Disconnect;
DataModule1.ZConnection1.Protocol := 'SQLite-3';
DataModule1.ZConnection1.Database :=baseDir + currdb;
DataModule1.Query1.SQL.Clear;
DataModule1.Query1.SQL.Add('SELECT * FROM MyTable'); // <<<<--- ZConnection1 is Query1 database connection.
DataModule1.ZConnection1.Connect; // <<<<<-------Here is where I get ACCESS VIOLATION all the time.
UpdateGrid; // <<<<<<<----- Here is where the Query is executed and the DBGrid is updated.
end;
end;
I don't know why this is. Is this mean I can't switch database from another using Zeoslib component?
The problem is in the TZConnection.Protocol value capitalization. Change the Protocol value from SQLite-3 to sqlite-3.
I have an old Delphi 7 application that loads data from one database table, make many operations and calculation and finally writes records to a destination table.
This old application calls ApplyUpdates every 500 records, for performances reasons.
The problem is that, sometimes, in this bunch of records lies one that will trigger database constraint; Delphi fires an exception on ApplyUpdates.
My problem is I don't know which record is responsible for this exception. There are 500 candidates!
Is it possible to ask TClientDataset which is the offending record?
I do not want to ApplyUpdates foreach appended record for speed issues.
I think you may try to implement the OnReconcileError event which is being fired once for each record that could not be applied to the dataset. So I would try the following code, raSkip means here to skip the current record:
procedure TForm1.ClientDataSet1ReconcileError(DataSet: TCustomClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin
Action := raSkip;
ShowMessage('The record with ID = ' + DataSet.FieldByName('ID').AsString +
' couldn''t be updated!' + sLineBreak + E.Context);
end;
But please note, I've never tried this before and I'm not sure if it's not too late to ignore the errors raised by the ApplyUpdates function. Forgot to mention, try to use the passed parameter DataSet which should contain the record that couldn't be updated; it might be the way to determine what record caused the problem.
And here is described the updates applying workflow.
Implementing OnReconcileError will give you access to the record and data that is responsible for the exception. An easy to accomplish this is to add a “Reconcile Error Dialog”. It is located on the “New Items” dialog which is displayed by File | New | Other. Once you have added it to your project and used it in the form with the clientdataset. The following code shows how it is invoked.
procedure TForm1.ClientDataSetReconcileError(DataSet: TCustomClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction);
begin
Action := HandleReconcileError(DataSet, UpdateKind, E);
end;
It will display instead of the exception dialog. It will allow you to view the offending data and select how you want to proceed. It has been over 5 years since I last used it, hopefully I have not forgotten some details.
I have 3 TQueries: qy_master, qy_detail, qy_detail2, master of qy_detail2 is qy_detail, master of qy_detail is qy_Master, all queries have corresponding data-sources, I placed queries in datamodule, when datamodule create I activate those queries.
In another form I used those queries, when trying 'qy_detail.open' it says 'EDbengine error : Tables is read-only' but no problem when opening qy_detail, I don't modify SQL statements, but I don't know why this error happens.
I also tried with qy_detail.Active := True; this statement also raise error,
I used SQL Server 2005 connected via BDE and ODBC datasources.
Please anyone help me to fix this.
Have you set TQuery.RequestLive = true? RequestLive is False by default forcing query to always return as read-only result set.
From documentation:
A TQuery can return two kinds of result sets: "live" as with TTable
component (users can edit data with data controls, and when a call to
Post occurs changes are sent to database), "read only" for display
purposes only. To request a live result set, set a query component's
RequestLive property to True...
Recently I met a strange problem, see code snips as below:
var
sqlCommand: string;
connection: TADOConnection;
qry: TADOQuery;
begin
connection := TADOConnection.Create(nil);
try
connection.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Test.MDB;Persist Security Info=False';
connection.Open();
qry := TADOQuery.Create(nil);
try
qry.Connection := connection;
qry.SQL.Text := 'Select * from aaa';
qry.Open;
qry.Append;
qry.FieldByName('TestField1').AsString := 'test';
qry.Post;
beep;
finally
qry.Free;
end;
finally
connection.Free;
end;
end;
First, Create a new access database named test.mdb and put it under the directory of this test project, we can create a new table named aaa in it which has only one text type field named TestField1.
We set a breakpoint at line of "beep", then lunch the test application under ide debug mode, when ide stops at the breakpoint line (qry.post has been executed), at this time we use microsoft access to open test.mdb and open table aaa you will find there are no any changes in table aaa, if you let the ide continue running after pressing f9 you can find a new record is inserted in to table aaa, but if you press ctrl+f2 to terminate the application at the breakpoint, you will find the table aaa has no record been inserted, but in normal circumstance, a new record should be inserted in to the table aaa after qry.post executed.
who can explain this problem , it troubles me so long time. thanks !!!
BTW, the ide is delphi 2010, and the access mdb file is created by microsoft access 2007 under windows 7
Access won't show you records from transactions that haven't been committed yet. At the point where you pause your program, the implicit transaction created by the connection hasn't been committed yet. Haven't experimented, but my guess would be that the implicit transaction will be committed after you free the query. So if you pause just after that, you should see your record in MS Access.
After more information from Ryan (see his answer to himself), I did a little more investigating.
Having a primary key (autonumber or otherwise) doesn't seem to affect the behaviour.
Table with autonumber column as primary key
connection.Execute('insert into aaa (TestField1) values (''Test'')');
connection.Execute('select * from aaa');
connection.Execute('delete * from aaa');
beep;
finally
connection.Free;
end;
Stopping on the "select" does not show the new record.
Stopping on the "delete" shows the new record.
Stopping on the "beep" still shows all records in the table even after repeated refresh's.
Stopping on the "connection.Free" shows no more records in the table. Huh?
Stopping on a "select" inserted between the "delete" and the "beep" shows no more records in the table.
Same table
connection.Execute('insert into aaa (TestField1) values (''Test'')');
beep;
connection.Execute('delete * from aaa');
beep;
beep;
Stopping on each statement shows that Access doesn't receive the "command" until at least one other statement has been executed. In other words: the beep after the "Execute" statement must have been processed before the statement is processed by Access (it may take a couple of refreshes to show up, the first refresh isn't always enough). If you stop on the first beep after the "Execute" statement nothing has happened in Access and won't if you reset the program without executing any other statements.
Stepping into the connection.Execute (Use debug dcu's on): the effect of the executed sql statement is now visible in Access on return to the beep. Actually, it is visible much earlier. For example stepping into the "delete" statement, the record becomes marked #deleted somewhere still in the ADODB code.
In fact, when stepping through the adodb code, the record becomes visible in Access when stopped in the OnExecuteComplete handler. Not when stopped on the "begin", but when stopped on the "if Assigned" immediately thereafter. The same applies to the delete statement. The effect becomes visible in Access when stopped on the if statement in the OnExecuteComplete handler in AdoDb.
Ado does have an ExecuteOption to execute statements asynchronously. It wasn't in effect during all this (its not included by default). And while we are dealing with an out-of-process COM server and with call backs such as the OnExecuteComplete handler, that handler was executed before returning to the statement right after the ConnectionObject.Execute statement in the TAdoConnection.Execute method in AdoDb.
All in all I think it isn't so much a matter of synchronous or asynchronous execution, but more a matter of when references are released (we are dealing with COM and interface reference counting), or with thread and process timing issues (in app, Access and between them), or with a combination thereof.
And the debugger may just be muddling things more than clarifying them. Would be interesting to see what happens in D2010 with its single thread debugging capabilities, but haven't got it available where I am (now and for the next two weeks).
First , Marjan, Thank you for your answer, I am very sure I had clicked the refesh button in that time, but there was still nothing changed....
After many experiments, I found that if I inserted an auto-increment id into table fields as primary key , this strange behaviour would not happen, although i have done this , there is another strange behaviour , I will show my code snips , as below:
procedure TForm9.btn1Click(Sender: TObject);
var
sqlCommand: string;
connection: TADOConnection;
begin
connection := TADOConnection.Create(nil);
try
connection.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Test.MDB;Persist Security Info=False';
connection.Open();
connection.Execute('insert into aaa (TestField1) values (''Test'')');
connection.Execute('select * from aaa');
connection.Execute('delete * from aaa'); // breakpoint 1
beep; // breakpoint2
finally
connection.Free;
end;
end;
Put two breakpoints at line “delete” and “beep”, when codes stoped at breakpoint1, you can refresh the database , and you would find that the record was inserted, continue running when the codes stoped at the breakpoint2, you would find the record was still in there..... If at this time you pressed ctrl+f2, the record would be not deleted.... if connection.execute is a real sychronouse procedure , this should not happend. sorry for checking your answer so late, because i am on our dragon boat festival...
Marjan, thanks for your response again, but i can't accept this behaviour what the connection enginee processes, today I find something useful on MSDN website, see:
http://msdn.microsoft.com/en-us/library/ms719649(v=VS.85).aspx
I have resolved the problem fortunately according to the article, Actually, the default value of the property "Jet OLEDB:Implicit Commit Sync" is false, According to the explanation of this property, Be false implies that the implicit transaction will use asynchronouse mode. so what we can do is set this property be true by using code snips as below :
connection.Properties.Item['Jet OLEDB:Implicit Commit Sync'].Value := true;
BTW, according to that article, this property can only be set by using the Properties property of the connection object, otherwise if it is set in connection string, an error will occur