Child entities fail to save due to the parent entity's key not been passed to the child

Posts   
 
    
Posts: 22
Joined: 10-Aug-2012
# Posted on: 01-Oct-2012 17:27:55   

I'm using version 3.5.

We have a table structure as follows:

Batch
------
BatchId (int PK)


BatchItem
------
BatchId (int PK)
SubjectId (int PK)

The following code does not work as the BatchId is not added to the BatchItem child entities:

Batch batch = new Batch(); batch.BatchItems.Add(new BatchItem() { SubjectId = 1 });

batch.BatchItems.Add(new BatchItem() { SubjectId = 2 });

When saving using adapter.SaveEntity(batch, true, true); I get an exception saying that it can't insert Null into the BatchItem table. Sure enough on the QueryExcecuted, the Insert statement for the BatchItems is missing the BatchId.

Am I just expecting too much from LLBLGen? We've worked around the problem for the moment by saving a blank Batch entity and adding the BatchItems afterwards. It'd obviously be much nicer if we could just do it all through the one SaveEntity call.

Edit: Additionally, our workaround didn't work initially as if we didn't set a BatchId, it wouldn't insert an entity. If we just set the BatchId to "0" and try the workaround again, it works fine. It also appears that with the original example that fails, that also works if we set the BatchId to 0 and save. (NB the BatchId is returned correctly i.e. 10 if we set it to 0.)

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 01-Oct-2012 17:56:07   

Of-Course this should work.

The issue as I see it from your code, is the parent (Batch) is not inserted in the first place. Because no field has been set. If none of the entity's fields is set/changed, no Insert statement will be executed. So you have to edit at least one field.

That's why when you set the PK to 0, the framework detects one field being dirty and thus it inserts the entity, following from there a correct PK is returned and use in the inserts of the child entities.

Posts: 22
Joined: 10-Aug-2012
# Posted on: 01-Oct-2012 23:03:00   

Seems like a bug to me. If there's no fields that I should be setting and only a PK which is an auto-incrementing integer, then it should know to generate a new record.

It should be as simple as detecting, on save, that the entity only has the auto incrementing integer and no other fields and just firing off:

INSERT INTO Batch DEFAULT VALUES

.

Just a suggestion! We're still evaluating LLBLGen and it seems really wrong to have to do:


Batch entity = new Batch();
entity.BatchId = 0;

Adapter.SaveEntity(entity, true);

When we never have to specify "0" anywhere else. I understand why we have to, but it just seems inconsistent.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 02-Oct-2012 07:20:33   

GenericTypeTea wrote:

Seems like a bug to me. If there's no fields that I should be setting and only a PK which is an auto-incrementing integer, then it should know to generate a new record.

It should be as simple as detecting, on save, that the entity only has the auto incrementing integer and no other fields and just firing off:

INSERT INTO Batch DEFAULT VALUES

.

I understand your point, but it's not that simply. The problem is: it doesn't know the information if the field is an identity field, because that info is DB Specific and not stored in the entity info, which is the info the code works with. Remember that an entity is a disconnected object which is filled when you fetch it and it's passed back to a save action if it's a candidate for a save (i.e.:it's dirty). So it can't decide at that point if an entity is saveble because of the identity flag, as it doesn't have that info. I.o.w.: it can only look at the entity itself and it sees an empty entity so it doesn't accept that entity for save.

So, although we understand your perspective, rather than a bug this is a LLBLGen design principle. Also, we don't know if you Batch table has only one field, that is not supported by almost all DBs, I think SQLServer supported it sometime ago, I don't know about recent versions though. Workaround always seems fishy, but they are for edge cases, like this.

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39898
Joined: 17-Aug-2003
# Posted on: 02-Oct-2012 09:54:01   

Some background: The entity 'Batch' is totally empty, and as such, not a candidate to save. This is done to prevent phantom inserts, e.g. due to databinding entities are added to collections or other references of the entity graph and although they're totally empty, they're still saved.

You want to save the entity even though it's empty. You can, but you have to signal it's not 'empty':

[Test]
public void InsertWithDefaultsTest()
{
    using(var adapter = new DataAccessAdapter())
    {
        var toInsert = new OrderEntity();
        try
        {
            toInsert.Fields.IsDirty = true;
            Assert.IsTrue(adapter.SaveEntity(toInsert));
            Assert.IsTrue(toInsert.OrderId > 0);
        }
        finally
        {
            Assert.IsTrue(adapter.DeleteEntity(toInsert));
        }
    }
}

Here I mark it as 'dirty'. This will generate:

RT version: 3.5.0.0.08242012
Method Enter: CreateInsertDQ
Method Enter: CreateSingleTargetInsertDQ
Generated Sql query: 
    Query: INSERT INTO [Northwind].[dbo].[Orders] DEFAULT VALUES ;SELECT @p1=SCOPE_IDENTITY()
    Parameter: @p1 : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: <undefined value>.

Method Exit: CreateSingleTargetInsertDQ
Method Exit: CreateInsertDQ
Method Enter: CreateDeleteDQ(4)
Method Enter: CreateSingleTargetDeleteDQ(3)
Method Enter: CreateSingleTargetDeleteDQ(4)
Generated Sql query: 
    Query: DELETE FROM [Northwind].[dbo].[Orders] WHERE ( [Northwind].[dbo].[Orders].[OrderID] = @p1)
    Parameter: @p1 : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 13216.

Method Exit: CreateSingleTargetDeleteDQ(4)
Method Exit: CreateSingleTargetDeleteDQ(3)
Method Exit: CreateDeleteDQ(4)

1 passed, 0 failed, 0 skipped, took 2,73 seconds (NUnit 2.5.5).

As you can see, it does generate the desired query you want, but it only does that if it's explicitly told to accept the empty entity container as being 'a valid entity' as it isn't a valid entity without the dirty flag being false, as David described above. Once it's been accepted as 'valid' for persistence, it goes into the pipeline and there the persistence info is available which contains the identity info.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 22
Joined: 10-Aug-2012
# Posted on: 02-Oct-2012 11:40:20   

Thank you for the in depth explanation. Our 'Batch' table is a bit of a hack in itself, so I suppose it is very much an edge case. And Otis, that's a more acceptable workaround as it actually feels like we're telling LLBLGen to do some specific as apposed to randomly sticking a fake ID in. We'll use that in future.

Thanks again smile