2

I'm trying to persist two tables with master-detail relationship in MySQL 5.6 using Delphi XE3 and Zeos 7.0.4. When I do ApplyUpdates on the master, the auto increment field stays with 0 as value. I need the auto increment value, so I can link the detail table with the master table's ID field coming from ApplyUpdates. I'm using ZConnection with AutoCommit = FALSE and TransactionIsolationLevel = tiReadCommitted, ZQuery with CachedUpdates = TRUE. What am I missing?

ZQPerson.Append;
ZQEmployee.Append;
try
  ZQPersonName.Value := Edit1.Text;
  ZQPerson.ApplyUpdates; //Here I expected to have the auto increment value on the Id field of ZQPerson, but it returns always 0
  ZQEmployeePersonID.Value := ZQPersonId.Value; //Here I'd link Employee to it's Person record
  ZQEmployeeRegNo.Value := StrToInt(Edit2.Text);
  ZQEmployee.ApplyUpdates;
  ZConnection1.Commit; //Here I would persist both tables in a single transaction to avoid master table without details
except
  ZQPerson.CancelUpdates;
  ZQEmployee.CancelUpdates;
  ZConnection1.Rollback; //In case of exceptions rollback everything
  raise;
end;
ZQPerson.CommitUpdates;
ZQEmployee.CommitUpdates;

My ZSQLMonitor trace is this:

2013-08-29 00:01:23 cat: Execute, proto: mysql-5, msg: INSERT INTO person (Id, name) VALUES (NULL, 'Edit1') --> This is just after ZQPerson.ApplyUpdates
2013-08-29 00:01:50 cat: Execute, proto: mysql-5, msg: INSERT INTO employee (Id, RegNo, ProductId) VALUES (NULL, 1000, 0), errcode: 1452, error: Cannot add or update a child row: a foreign key constraint fails (`test`.`employee`, CONSTRAINT `FK_A6085E0491BDF8EE` FOREIGN KEY (`PersonId`) REFERENCES `person` (`Id`) --> This is just after ZQEmployee.ApplyUpdates
2013-08-29 00:02:05 cat: Execute, proto: mysql-5, msg: Native Rollback call --> Rollback after Exception on the ZQEmployee.ApplyUpdates
Leandro Jacques
  • 413
  • 5
  • 19

3 Answers3

2

Are you starting the transaction with ZConnection1.StartTransaction? I think too that you must Refresh ZQuery1 after calling ZQuery1.ApplyUpdates to get the new id-

Reading your comment, you must be doing a select * without a where clause? right? I can recommend you use this approach:

1) select and increment the current autoincrement value
2) select from master table where id=[step1 id] // it will be empty, of course
3) add detail using the id in step 1
4) assign the id in the master dataset
5) apply both updates

José Romero
  • 462
  • 4
  • 9
  • The problem is that Refresh will point to the first record in the dataset and will not return the next auto increment value. – Leandro Jacques Aug 29 '13 at 02:25
  • How do I select the current auto increment value to accomplish step 1? – Leandro Jacques Aug 29 '13 at 04:19
  • I'm using Select * from product for ZQProduct and Select * from category, product where category.ProductId = product.Id for ZQCategory it's just to test master-detail relationship, not to do the relationship the correct way. – Leandro Jacques Aug 29 '13 at 04:23
  • See http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id – José Romero Aug 29 '13 at 04:23
  • The way you suggested, using last_insert_id solves the problem, but you don't need to increment the value that came from the first step. I made a workaround that worked fine, but it's not the ideal solution for the problem as it's not transparent the use of the database's auto increment feature. – Leandro Jacques Aug 29 '13 at 20:13
0

The workaround I found was this one. It not satiesfies me completely because it doesn't make transparent the use of the database's auto increment feature, making me use Last_Insert_ID() function. I'm in contact with zeos develpers to check this out.

function LastInsertID(ATableName: string): Integer;
var DBQuery: TZQuery;
begin
  DBQuery := TZQuery.Create(Self);
  with DBQuery do
  begin
    Connection := ZConnection1;
    SQL.Clear;
    SQL.Add('Select Last_Insert_ID() as Last_Insert_ID from ' + ATableName);
    Open;
    Result := FieldByName('Last_Insert_ID').Value;
    Free;
  end;
end;

procedure Persist;
var LastID: Integer;
begin
  ZQPerson.Append;
  ZQEmployee.Append;
  try
    ZQPersonName.Value := Edit1.Text;
    ZQPerson.ApplyUpdates; // Here I expected to have the auto increment value on the Id field of ZQPerson, but it returns always 0
    LastID := LastInsertID('Person'); //Getting the Last_Insert_ID(), even on the uncommitted transction, works
    ZQEmployeePersonId.Value := LastID; //Link the two tables using the Last_Insert_ID() result
    ZQEmployeeRegNo.Value := StrToInt(Edit2.Text);
    ZQEmployee.ApplyUpdates;
    ZConnection1.Commit; // Here I persist both tables in a single transaction to avoid master table without details
  except
    ZQPerson.CancelUpdates;
    ZQEmployee.CancelUpdates;
    ZConnection1.Rollback; // In case of exceptions rollback everything
    raise;
  end;
  ZQPerson.CommitUpdates;
  ZQEmployee.CommitUpdates;
Leandro Jacques
  • 413
  • 5
  • 19
0

I tested it in a simple database with two master and detail tables nested with TDataSource and relating by the where of the detail table:

object conMysql: TZConnection
     TransactIsolationLevel = tiReadCommitted
object zqryMaster: TZQuery
     Connection = conMysql
SQL.Strings = (
       'select * from temp.master')
object dsNestedMaster: TDataSource
     DataSet = zqryMaster
object zqryDetail: TZQuery
     Connection = conMysql
     SQL.Strings = (
       'select * from temp.detail'
       'where id_master =: id')

After starting the transaction all updates must wait for confirmation or rollback if an error occurs:

  try
    zqryMaster.Connection.StartTransaction;
    zqryMaster.Edit;
    zqryDetail.Edit;
    zqryMaster.FindField('dt_mov').Value := Now;
    while not zqryDetail.Eof do
    begin
      zqryDetail.Edit;
      zqryDetail.FindField('dt_mov').Value := Now;
      zqryDetail.ApplyUpdates;
      zqryDetail.Next;
      //raise Exception.Create('simple error'); //use for tests, check database after perform
    end;
    zqryMaster.ApplyUpdates;
    zqryMaster.Connection.Commit;
  except
    zqryMaster.Connection.Rollback;
    zqryMaster.CancelUpdates;
    zqryDetail.CancelUpdates;
  end;
  • 2
    Consider adding a sentence or two explaining how the code you provided fixes the problem OP asked the question about. – BenWillkommen Apr 11 '20 at 20:15
  • Instead of make the detail changes client-side with your while not eof loop, why not update all the detail rows with a single UPDATE? – MartynA Apr 12 '20 at 11:19
  • Yes, of course, use the same technique with the TZSQLProcessor component, the same `try-except` block, cause the same result by executing just one update command, or a list of them if necessary, start the transaction from the connection, enter the commands in the sql script, execute and confirm, or roll back if an error occurs. – Nélio Jório Apr 13 '20 at 05:01