Subject Re: [IBO] saving atomic data :)
Author Helen Borrie (TeamIBO)
At 10:02 PM 10-02-02 +0100, you wrote:
>I want to save an invoice which is made of a header and N detail rows (with
>N>=1).
>
>The user inputs invoice data via a normal master/detail form with 2 grids,
>2 navigators, 2 queries and 2 datasources.
>
>I need to prevent:
>
>1. an orphaned header (i.e. that a header is inserted with no detail row)

The "database" way to prevent this by using a trigger on the header to disallow a 1:0 relationship - but this can interfere with IBO's handling of master-detail.

>2. an orphaned detail row (i.e. that one or more detail rows are inserted
>with no header)

The database way is to enforce the relationship with a foreign key on the detail that points to the primary key on the master. This is recommended for robustness, anyway, in case detail rows are entered via a database tool.

If you have the Mastersource and MasterLinks set up properly, this is done for you. IBO posts the master record first and performs a CommitRetaining on the master table. (If a Cancel occurs later, IBO goes back and deletes this record).

>If I had to test condition 1. only, I would insert the detail rows first,
>then perform the check in the BeforePost event of the header table.

Condition 1 is to enforce 1:1-to-Many, right? Because IBO does a "blind" post first, this test would always cause the blind post to fail.

I think your enforcement test should be on the detail dataset which, with MasterSource/MasterLinks defined, will be in dssInsert when you are inserting into the master or when the detail dataset is empty. Write a handler to test the IsEmpty property of the detail dataset, which you can call in its BeforePost event to enforce the obligatory relationship (I suggest BeforePost rather than BeforeInsert, because you will want to enforce the relationship in subsequent delete operations as well).

In the case of an Insert, the new record is already created in the buffer. I think the ib_datasource of the detail does magic of its own (through its DataLinkList property) during AfterInsert to determine whether the user has added data and decide whether the insert should actually be posted.


>If I had to test condition 2. only, I would insert the header first, then
>perform the check in the BeforePost event of the detail table.
>
>However, I cannot understand how I can easily test both conditions.
>
>I have deviced a clever method so that the AfterInsert event of the header
>table inserts a new record in the detail table, then the AfterPost event of
>the header table issues a Post of the detail table, etc.

You might be "double-dipping" here since IBO already handles this by inserting the first detail record automatically. I think your only concern is to ensure that the user has modified the record before the second Post of the master dataset occurs.


>But I can see tons of subtle problems.
>
>I thought to use the BeforeCommit event of the current transaction, but
>several invoices could have been inserted by the user before he issues a
>commit, and I don't know how to go back to everyone of them.

You can read the Datasets property of the transaction if you want to attack the problem at the transaction level; but this might be more awkward for the user than using IBO's own devices to intercept an empty detail dataset.

>On top of it, it would be very unconvenient to have to rollback several
>input invoices.

Agreed: it should be done at Post, not at Commit.

If you are using TDataset-compatible components, I think there will be a bit more work to do...


regards,
Helen Borrie (TeamIBO Support)

** Please don't email your support questions privately **
Ask on the list and everyone benefits
Don't forget the IB Objects online FAQ - link from any page at www.ibobjects.com