LLBLGen 4 concurrency problem on saving inherited object

Posts   
 
    
Posts: 19
Joined: 27-Mar-2011
# Posted on: 26-Apr-2013 16:11:30   

LLBLGen 4.0.13.0422, Adapter Postgres 9.2 Visual Studio 2010, .NET 4.0 Windows 7

I'm testing 4.0 version and I have problem when saving object that have inheritance hierarchy.

On save LLBLGen is generating two updates; on for base entity and one for inherited entity. In 3.x versions there was concurrency check only for base entity update. In 4.0 version there is concurrency check for both entities.

When base entity is updated, his concurrency field is also updated. So, on inherited entity update concurrency check is failing, 'cos there is new value in concurrency field.

v 4.0 update:


{IActionQuery #0 :

    Query: UPDATE "public"."Pacijent" LLBL_UT__0 SET "ConcurrencyGuid"=:p1 
        WHERE EXISTS (SELECT * FROM ( "public"."Pacijent"  INNER JOIN "public"."PacijentDetalji"  ON  "public"."Pacijent"."PacijentId"="public"."PacijentDetalji"."PacijentId") 
        WHERE LLBL_UT__0."PacijentId"="public"."Pacijent"."PacijentId"  AND ( ( "public"."Pacijent"."PacijentId" = :p2) AND ( ( "public"."Pacijent"."ConcurrencyGuid" = :p3))) )
    Parameter: :p1 : String. Length: 50. Precision: 0. Scale: 0. Direction: Input. Value: "8bc13f86-4fb7-420e-95cc-8d659519e5e3".
    Parameter: :p2 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 3906.
    Parameter: :p3 : String. Length: 50. Precision: 0. Scale: 0. Direction: Input. Value: "0eecc572-5519-4b53-b410-499e03f2c473".


IActionQuery #1 :

    Query: UPDATE "public"."PacijentDetalji" LLBL_UT__0 
        SET "RadniStatusNavropId"=:p4 
            WHERE EXISTS (SELECT * FROM ( "public"."Pacijent"  INNER JOIN "public"."PacijentDetalji"  ON  "public"."Pacijent"."PacijentId"="public"."PacijentDetalji"."PacijentId") 
            WHERE LLBL_UT__0."PacijentId"="public"."Pacijent"."PacijentId"  AND ( ( "public"."PacijentDetalji"."PacijentId" = :p5) AND ( ( "public"."Pacijent"."ConcurrencyGuid" = :p6))) )
    Parameter: :p4 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 379.
    Parameter: :p5 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 3906.
    Parameter: :p6 : String. Length: 50. Precision: 0. Scale: 0. Direction: Input. Value: "0eecc572-5519-4b53-b410-499e03f2c473".


}

v 3.5 update:


{IActionQuery #0 :

    Query: UPDATE "public"."Pacijent" LLBL_UT__0 SET "ConcurrencyGuid"=:p1 
        WHERE EXISTS (SELECT * FROM ( "public"."Pacijent"  INNER JOIN "public"."PacijentDetalji"  ON  "public"."Pacijent"."PacijentId"="public"."PacijentDetalji"."PacijentId") 
        WHERE LLBL_UT__0."PacijentId"="public"."Pacijent"."PacijentId"  AND ( ( "public"."Pacijent"."PacijentId" = :p2) AND ( "public"."Pacijent"."ConcurrencyGuid" = :p3)) )
    Parameter: :p1 : String. Length: 50. Precision: 0. Scale: 0. Direction: Input. Value: "75cf9de7-e971-4715-be4a-9a37ceabad77".
    Parameter: :p2 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: -8175.
    Parameter: :p3 : String. Length: 50. Precision: 0. Scale: 0. Direction: Input. Value: "69c923b7-c3fc-47ff-985c-f2de5ea8da90".


IActionQuery #1 :

    Query: UPDATE "public"."PacijentDetalji" SET "RadniStatusNavropId"=:p4 WHERE ( ( "public"."PacijentDetalji"."PacijentId" = :p5))
    Parameter: :p4 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 378.
    Parameter: :p5 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: -8175.


}

Regards,

Marin

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 26-Apr-2013 16:56:53   

The code is roughly the same in that area, so it's odd that this happens. Could you give us code snippets so we can see how you're passing the filter ? I assume you pass it as a save restriction filter, either manually or through a concurrencypredicate factory, correct?

The point is: looking at the code, it always adds the additional filter to all queries, in v3.5 and in v4.0. (DynamicQueryEngineBase.cs, line 804 in v3.5, line 807 in v4). This has been this way (the code at least) since a very long time.

I'll run a unit test to see what it does in practice simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 26-Apr-2013 17:21:03   

I can reproduce it in v4. (SQL Server, doesn't matter, filter is added is in generic code)


[Test]
public void UpdateClerkWithRestriction()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        var clerk = new ClerkEntity();
        // Employee's fields
        clerk.Name = "Frans Bouma";
        clerk.StartDate = DateTime.Now;
        clerk.JobDescription = "Descr 1";
        // rest is null.

        Assert.IsTrue(adapter.SaveEntity(clerk, true));

        var refetchedClerk = new ClerkEntity(clerk.Id);
        try
        {
            Assert.IsTrue(adapter.FetchEntity(refetchedClerk));

            // create predicate based on name
            var concurrencyFilter = ClerkFields.Name == "Frans Bouma";

            // update name, which is in employee.
            refetchedClerk.Name = "Frans Bouma Changed";
            refetchedClerk.JobDescription = "Descr 2";

            // update 
            Assert.IsTrue(adapter.SaveEntity(refetchedClerk, true, new PredicateExpression(concurrencyFilter)));
        }
        finally
        {
            // delete it
            Assert.IsTrue(adapter.DeleteEntity(refetchedClerk));
        }
    }
}

but... I can also reproduce the same issue on v3.5. Queries are the same. The test above crashes (in v4 as well) with: (from v3.5 tests)

Test 'Unittests.TestLibrary.SqlServerTests.Adapter.InheritanceOneUnitTests.UpdateClerkWithRestriction' failed: SD.LLBLGen.Pro.ORMSupportClasses.ORMConcurrencyException : During a save action an entity's update action failed. The entity which failed is enclosed.
    C:\Myprojects\VS.NET Projects\LLBLGen Pro v3.5\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs(1401,0): at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.PersistQueue(List`1 queueToPersist, Boolean insertActions, Int32& totalAmountSaved)
    C:\Myprojects\VS.NET Projects\LLBLGen Pro v3.5\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs(1271,0): at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.SaveEntity(IEntity2 entityToSave, Boolean refetchAfterSave, IPredicateExpression updateRestriction, Boolean recurse)
    C:\Myprojects\VS.NET Projects\LLBLGen Pro v3.5\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs(1146,0): at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.SaveEntity(IEntity2 entityToSave, Boolean refetchAfterSave, IPredicateExpression updateRestriction)
    InheritanceOneTests.cs(74,0): at Unittests.TestLibrary.SqlServerTests.Adapter.InheritanceOneUnitTests.UpdateClerkWithRestriction()

Queries: q1:

UPDATE [InheritanceOne].[dbo].[Employee]
SET [Name] = 'Frans Bouma Changed' /* @p1 */
FROM   ( [InheritanceOne].[dbo].[Employee]
         INNER JOIN [InheritanceOne].[dbo].[Clerk]
             ON [InheritanceOne].[dbo].[Employee].[EmployeeID] = [InheritanceOne].[dbo].[Clerk].[ClerkID])
WHERE  (([InheritanceOne].[dbo].[Employee].[EmployeeID] = 106454 /* @p2 */)
    AND (([InheritanceOne].[dbo].[Employee].[Name] = 'Frans Bouma' /* @p3 */)))

q2:

UPDATE [InheritanceOne].[dbo].[Clerk]
SET [JobDescription] = 'Descr 2' /* @p4 */
FROM   ( [InheritanceOne].[dbo].[Employee]
         INNER JOIN [InheritanceOne].[dbo].[Clerk]
             ON [InheritanceOne].[dbo].[Employee].[EmployeeID] = [InheritanceOne].[dbo].[Clerk].[ClerkID])
WHERE  (([InheritanceOne].[dbo].[Clerk].[ClerkID] = 106454 /* @p5 */)
    AND (([InheritanceOne].[dbo].[Employee].[Name] = 'Frans Bouma' /* @p6 */)))

so it's important which build of v3.5 you're using (likely an old one) and why it does work in your case but not here.

I'm not saying it should produce these queries however, I'm first trying to get the same repro case as you're working with .

Frans Bouma | Lead developer LLBLGen Pro
Posts: 19
Joined: 27-Mar-2011
# Posted on: 26-Apr-2013 22:36:27   

Sorry, it's not 3.5 I's 3.1 (3.1.11.0701)

Yes, predicate is set via ConcurrencyPredicateFactory:


    class OptimisticConcurrencyFactory : IConcurrencyPredicateFactory
    {
        public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
        {
            IPredicateExpression toReturn = null;

            if (predicateTypeToCreate == ConcurrencyPredicateType.Save)
            {
                toReturn = new PredicateExpression();
                CommonEntityBase entity = (CommonEntityBase)containingEntity;
                EntityField2 concurrencyField = (EntityField2)entity.Fields["ConcurrencyGuid"];
                toReturn.Add(concurrencyField == concurrencyField.CurrentValue);
            }

            return toReturn;
        }
    }

And 'ConcurrencyField' is set in DataAccessAdapter:


        protected override void OnBeforeEntitySave(IEntity2 entitySaved, bool insertAction)
        {
            if (entitySaved.IsDirty)
            {
                entitySaved.Fields["ConcurrencyGuid"].CurrentValue = System.Guid.NewGuid().ToString();
            }

            base.OnBeforeEntitySave(entitySaved, insertAction);
        }

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 27-Apr-2013 08:32:40   

edgecrusher wrote:

Sorry, it's not 3.5 I's 3.1 (3.1.11.0701)

It's also an old one. Could you please download the latest one and try again? Just to confirm whether or not this happen definitely on v3.1.

David Elizondo | LLBLGen Support Team
Posts: 19
Joined: 27-Mar-2011
# Posted on: 28-Apr-2013 22:47:55   

daelmo wrote:

edgecrusher wrote:

Sorry, it's not 3.5 I's 3.1 (3.1.11.0701)

It's also an old one. Could you please download the latest one and try again? Just to confirm whether or not this happen definitely on v3.1.

It's not 3.1 one that causes trouble. It's 4.0 (latest).

You have update scripts in first post. Only error is that I've labeled second update script as '3.5'. That script is generated by '3.1'.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 29-Apr-2013 16:57:17   

The issue is a result of a bugfix, added January 8th, to v3.5 (so v3.1 will always work) and which is ported to v4 as well.

The issue is:

When executing an update directly on a subtype in a TPE hierarchy with an additional filter, it could be the update would update all entities in the subtype table as no extra filter was applied to the query.

To fix this we added the additional filter to all queries. This goes wrong in your particular situation where the filter will not match any rows in subtype updates because the value has changed in the first query executed. With the situation fixed above, this isn't the case.

It's a complex case, as the filter is specified with the value BEFORE it's set, and that filter is the only filter the code has, however the value is changed in the first query, so subsequential uses of the filter won't work, as they have an out-dated value. However, creating the filter with the new value will of course make the first query fail already so it's a catch-22.

Not adding the filter to the subtype update queries will make it work (as it did in v3.1 and before) however it will then re-introduce the bug we fixed in January 8th... disappointed We have to look at this and how to solve it...

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 29-Apr-2013 17:18:10   

I think we found a loophole simple_smile Stay tuned simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 29-Apr-2013 17:46:47   

Fixed! see attached dll (or download the installer we'll push to the website in one hour)

Attachments
Filename File size Added on Approval
SD.LLBLGen.Pro.ORMSupportClasses.zip 409,689 29-Apr-2013 17:46.56 Approved
Frans Bouma | Lead developer LLBLGen Pro
Posts: 19
Joined: 27-Mar-2011
# Posted on: 30-Apr-2013 10:20:51   

Works now. Thanks.

Don't know how you managed to cover both situations (it looks like catch 22, as you mentioned simple_smile ).

Cheers