views:

175

answers:

3

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

+1  A: 

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).

Marjan Venema
i expected that a new record was inserted after the qry.post executed , because the qry.post seems synchronouse executed, but according to the test, it was asynchronous executed, put two breakponts at line of "beep" and the line of "qry.free", i found when application paused at line of "beep"(qry.post have been executed) the new record was not inserted , but when application paused at the line of "qry.free", the record was inserted( qry.free was not executed), why the qry.post was not synchronous executed....
Ryan
Could it be that you are checking too soon or not refreshing the table/query in MS Access? I have just copied your code verbatim (just edited the connection) to a onclick handler of a button on a fresh vcl forms application and put a breakpoint on the beep. Started the app, clicked the button and when it hit the breackpoint, moved to MS Access where the newly created table with two initial records was still open. Just pressing refresh (F5) made the record newly inserted from the delphi app (still paused at the beep) show up. Using Access 2007 with a 'Microsoft.ACE.OLEDB.12.0' provider.
Marjan Venema
there is no enough charactor space to fill my messages, see the next answer
Ryan
I have got the answer,you can see my answer to myself, I am also appreciate for your help :)
Ryan
A: 

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...

Ryan
I'll have a look. In the meantime: hope the dragons don't get you!
Marjan Venema
Findings too long for a comment. See edit to my answer.
Marjan Venema
A: 

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

Ryan