Concurrency and Inheritance

Posts   
 
    
mikes
User
Posts: 14
Joined: 20-Sep-2005
# Posted on: 19-Jan-2006 16:53:30   

In our application we are tracking concurrency changes using a Timestamp field (SQL 2000) called EntityConcurrency in every application table in the database. (Self-Servicing Model)

I created a GenericConcurencyFactory to manage our basic tables (see below).


public class GenericConcurencyFactory : IConcurrencyPredicateFactory
{
    public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
    {
        IPredicateExpression toReturn = new PredicateExpression();
        IEntity entity = (IEntity)containingEntity;
        
        switch (predicateTypeToCreate)
        {
             case ConcurrencyPredicateType.Save:
                // only for updates
                toReturn.Add(new FieldCompareValuePredicate(entity.Fields["EntityConcurrency"], ComparisonOperator.Equal));                 
                break;
        }
        return toReturn;
    }

}

And it is used like this in the code.


UserClassEntity uce = new UserClassEntity(userclassid);
// some changes to the object 
uce.ConcurrencyPredicateFactoryToUse = new GenericConcurencyFactory();

try
{
    uce.Save();
} catch (ORMConcurrencyException cex)
{
    // Handle concurrency exception
}

This factory seems to be working fine for all tables that are not in a Sub-Type/Super-Type relationship.

Currently I have a sub-type / super-type relationship between two tables Party and Person. The GenericConcurencyFactory does not fail a save to an object when there is a concurrency violation.

I tried writing a Custom Checker for the person class that checks the person class.


public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
{
    IPredicateExpression toReturn = new PredicateExpression();
    PersonEntity person = (PersonEntity)containingEntity;

    switch (predicateTypeToCreate)
    {
        
            toReturn.Add(new FieldCompareValuePredicate(person.Fields[(int) PersonFieldIndex.EntityConcurrency], ComparisonOperator.Equal));
            toReturn.AddWithAnd(new FieldCompareValuePredicate(person.Fields[(int)PersonFieldIndex.EntityConcurrency_Party], ComparisonOperator.Equal));
            //break;                    
            break;
    }
    return toReturn;
}

I also wrote tried the Check like this:


public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
{
    IPredicateExpression toReturn = new PredicateExpression();
    PersonEntity person = (PersonEntity)containingEntity;

    switch (predicateTypeToCreate)
    {
        
        case ConcurrencyPredicateType.Save:
            // only for updates
            toReturn.Add(
                PredicateFactory.CompareExpression(
                    PersonFieldIndex.EntityConcurrency,
                    ComparisonOperator.Equal,
                    new Expression(person.Fields[(int)PersonFieldIndex.EntityConcurrency])
                    )
                );
            toReturn.AddWithAnd(
                 PredicateFactory.CompareExpression(
                    PersonFieldIndex.EntityConcurrency_Party,
                    ComparisonOperator.Equal,
                    new Expression(person.Fields[(int)PersonFieldIndex.EntityConcurrency_Party])
                    )

                );                  
            break;
    }
    return toReturn;
}

Neither works.

Is there an issue with overwritten properties in a class heirarchy? Am I approaching this wrong? Or do I just not understand the concurrency model properly?

Any help would be appreciated.

Thanks

Mike

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 19-Jan-2006 18:13:09   

Does it produce a filter fragment (please check that with tracing enabled), or is the factory simply ignored? (and thus no filter is added)

Frans Bouma | Lead developer LLBLGen Pro
mikes
User
Posts: 14
Joined: 20-Sep-2005
# Posted on: 19-Jan-2006 19:03:30   

I believe it is. I walked through the code in debug mode and it enters the Factory returns a non-null entity.

I turned on the tracing and here is the output from the save. I'm not sure exactly what I should be looking for.


Method Enter: EntityBase.Save(2)
Method Enter: DaoBase.PersistQueue
Method Exit: DaoBase.PersistQueue
Method Enter: DaoBase.PersistQueue
Method Enter: DaoBase.UpdateExisting(3)
Method Enter: CreateUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateUpdateDQ(5)
Method Enter: DaoBase.ExecuteActionQuery
Method Enter: Query.ReflectOutputValuesInRelatedFields
Method Exit: Query.ReflectOutputValuesInRelatedFields: no parameter relations.
Method Exit: DaoBase.ExecuteActionQuery
Method Exit: DaoBase.UpdateExisting(3)
Method Exit: DaoBase.PersistQueue
Method Exit: EntityBase.Save(2)
Method Enter: TransactionBase.Commit
Method Exit: TransactionBase.Commit

As a basis for comparison. Here is the trace from the factory that is working (Regular entities with no inheritance)


Method Enter: EntityBase.Save(2)
Method Enter: DaoBase.PersistQueue
Method Exit: DaoBase.PersistQueue
Method Enter: DaoBase.PersistQueue
Method Enter: DaoBase.UpdateExisting(3)
Method Enter: CreateUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateUpdateDQ(5)
Method Enter: DaoBase.ExecuteActionQuery
Method Exit: DaoBase.ExecuteActionQuery
Method Exit: DaoBase.UpdateExisting(3)
A first chance exception of type 'SD.LLBLGen.Pro.ORMSupportClasses.ORMConcurrencyException' occurred in SD.LLBLGen.Pro.ORMSupportClasses.NET20.DLL
Method Exit: EntityBase.Save(2)
A first chance exception of type 'SD.LLBLGen.Pro.ORMSupportClasses.ORMConcurrencyException' occurred in BusinessObjects.DLL

Thanks

Mike

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 19-Jan-2006 19:27:15   

You should enable tracing for the DQE of the database you're using. simple_smile You now enabled tracing for the ORM elements, though these don't log the query generated, the dqe tracers do.

Frans Bouma | Lead developer LLBLGen Pro
mikes
User
Posts: 14
Joined: 20-Sep-2005
# Posted on: 19-Jan-2006 19:55:01   

It would appear that the query is not generating properly.

Method Exit: EntityBase.Validate
Method Enter: DaoBase.UpdateExisting(3)
Method Enter: CreateUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(4)
Generated Sql query: 
    Query: UPDATE [dbo].[Person] SET [FirstName]=@FirstName,[LastName]=@LastName WHERE ( ( [dbo].[Person].[bmkPerson] = @BmkPerson1))
    Parameter: @FirstName : String. Length: 200. Precision: 0. Scale: 0. Direction: Input. Value: person New FirstName.
    Parameter: @LastName : String. Length: 200. Precision: 0. Scale: 0. Direction: Input. Value: person New Lastname.
    Parameter: @BmkPerson1 : Int64. Length: 0. Precision: 19. Scale: 0. Direction: Input. Value: 3499.

Method Exit: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateUpdateDQ(5)
Method Enter: DaoBase.ExecuteActionQuery
Method Enter: Query.ReflectOutputValuesInRelatedFields
Method Exit: Query.ReflectOutputValuesInRelatedFields: no parameter relations.
Query Execution Result: 2
Method Exit: DaoBase.ExecuteActionQuery
Query Execution Result: True
Method Exit: DaoBase.UpdateExisting(3)

For the non-inheritance tables it looks like it is working.

Method Exit: EntityBase.Validate
Method Enter: DaoBase.UpdateExisting(3)
Method Enter: CreateUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(4)
Generated Sql query: 
    Query: UPDATE [dbo].[UserClass] SET [Sort]=@Sort WHERE ( ( [dbo].[UserClass].[bmkUserClass] = @BmkUserClass1) AND ( ( [dbo].[UserClass].[EntityConcurrency] = @EntityConcurrency2)))
    Parameter: @Sort : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 66.
    Parameter: @BmkUserClass1 : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1497.
    Parameter: @EntityConcurrency2 : Binary. Length: 8. Precision: 0. Scale: 0. Direction: Input. Value: binary lob.

Method Exit: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateUpdateDQ(5)
Method Enter: DaoBase.ExecuteActionQuery
Query Execution Result: 0

Is this a result of the ConcurrencyFactory not producing a proper predicate or is the DQE engine just not producing the proper query?

The code is very similiar to the GenericConcurrencyFactory which works.

mikes
User
Posts: 14
Joined: 20-Sep-2005
# Posted on: 19-Jan-2006 20:20:31   

As an aside.

I used the genericConcurrencyFactory that works on regular enities with the Sub-Type/Super-Typed Enitities and captured the trace.

It still doesnot work and it created teh same query that the PersonConcurrencyfacotry Created.


Method Enter: DaoBase.UpdateExisting(3)
Method Enter: CreateUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(5)
Method Enter: CreateSingleTargetUpdateDQ(4)
Generated Sql query: 
    Query: UPDATE [dbo].[Person] SET [FirstName]=@FirstName,[LastName]=@LastName WHERE ( ( [dbo].[Person].[bmkPerson] = @BmkPerson1))
    Parameter: @FirstName : String. Length: 200. Precision: 0. Scale: 0. Direction: Input. Value: person New FirstName.
    Parameter: @LastName : String. Length: 200. Precision: 0. Scale: 0. Direction: Input. Value: person New Lastname.
    Parameter: @BmkPerson1 : Int64. Length: 0. Precision: 19. Scale: 0. Direction: Input. Value: 3512.

Method Exit: CreateSingleTargetUpdateDQ(4)
Method Exit: CreateUpdateDQ(5)
Method Enter: DaoBase.ExecuteActionQuery
Method Enter: Query.ReflectOutputValuesInRelatedFields
Method Exit: Query.ReflectOutputValuesInRelate


Mike

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 20-Jan-2006 07:10:41   

Is your sub-type / super-type relationship a Target-Per-Entity or a Target-Per-Entity-Hierarchy?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 20-Jan-2006 12:15:24   

Walaa wrote:

Is your sub-type / super-type relationship a Target-Per-Entity or a Target-Per-Entity-Hierarchy?

Currently I have a sub-type / super-type relationship between two tables Party and Person.

simple_smile

I'm setting up a test now.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 20-Jan-2006 12:36:17   

Ok, the information you gave is incomplete and therefore I can't setup a test at this point. You stated you have a hierarchy between two tables: Party and Person. Though the example you gave is UserClass.

For us to be able to reproduce we need detailed information about the hierarchy you're using in your test which fails so we can rebuild an example which represents what you're doing/using.

For example: in a 2-table hierarchy, say Party and Person, the first action query created always has the joins of the hierarchy and the extra filter (concurrency filter). Your query doesn't have that extra FROM clause in the update statement for the related hierarchy tables. So this gives me the idea that UserClass isn't in a hierarchy. Please specify the hierarchy, if possible with DDL SQL of the tables involved so we can rebuild the setup.

Frans Bouma | Lead developer LLBLGen Pro
mikes
User
Posts: 14
Joined: 20-Sep-2005
# Posted on: 20-Jan-2006 15:25:07   

First to clarify.

I provided two examples. the UserClass is not in a heirarchy. It was a control example to show that my Concurrency was working properly for an entity outside of a heirarchy.

I also have two concurrency factories. A GenericConcurrencyFactory i wrote that works with the Userclass entity and all other entities not in a heirarchy.

I also wrote a PersonConcurrencyFactory becaues I thought maybe I had to make a more explicit check. This factory does not work either with the PersonEntity.

Sorry for the confusion.

Here is the DDL For the three Tables


CREATE TABLE [Party] (
    [bmkPartyId] [bigint] IDENTITY (2000, 1) NOT NULL ,
    [Active] [bit] NOT NULL CONSTRAINT [DF_Party_Active] DEFAULT (1),
    [CreateDate] [datetime] NOT NULL CONSTRAINT [DF_Party_CreateDate] DEFAULT (getdate()),
    [EntityConcurrency] [timestamp] NULL ,
    CONSTRAINT [PK_Party] PRIMARY KEY  CLUSTERED 
    (
        [bmkPartyId]
    )  ON [PRIMARY] 
) ON [PRIMARY]
GO


CREATE TABLE [Person] (
    [bmkPerson] [bigint] NOT NULL ,
    [UniqueID] [nvarchar] (255)  NOT NULL ,
    [FirstName] [nvarchar] (200)  NOT NULL ,
    [LastName] [nvarchar] (200)  NOT NULL ,
    [Middle] [nchar] (1)  NULL ,
    [JobTitle] [nvarchar] (255)  NULL ,
    [EmailAddress] [nvarchar] (255)  NULL ,
    [DateOfBirth] [datetime] NULL ,
    [Gender] [nvarchar] (50)  NULL ,
    [Active] [bit] NULL CONSTRAINT [DF_tblTrainee_Active] DEFAULT (0),
    [fgnReportsTo] [bigint] NULL ,
    [LogonID] [varchar] (200)  NULL ,
    [HomePhone] [nvarchar] (15)  NULL ,
    [WorkPhone] [nvarchar] (15)  NULL ,
    [MobilePhone] [nvarchar] (15)  NULL ,
    [CultureIdentifier] [varchar] (10)  NOT NULL CONSTRAINT [DF__Person__CultureI__1A9EF37A] DEFAULT ('en-US'),
    [UserName] [varchar] (50)  NULL ,
    [Password] [varchar] (50)  NULL ,
    [CreateDate] [datetime] NOT NULL CONSTRAINT [DF__Person__CreateDa__603D47BB] DEFAULT (getdate()),
    [EntityConcurrency] [timestamp] NULL ,
    [ModifyDate] [datetime] NOT NULL CONSTRAINT [DF__Person__ModifyDa__74444068] DEFAULT (getdate()),
    CONSTRAINT [PK_Person_bmkPerson] PRIMARY KEY  CLUSTERED 
    (
        [bmkPerson]
    ) WITH  FILLFACTOR = 90  ON [PRIMARY] ,
    CONSTRAINT [AK_Person_LogonID] UNIQUE  NONCLUSTERED 
    (
        [LogonID]
    )  ON [PRIMARY] ,
    CONSTRAINT [NK_Person_UniqueID] UNIQUE  NONCLUSTERED 
    (
        [UniqueID]
    )  ON [PRIMARY] ,
    CONSTRAINT [FK_Person_fgnReportsTo] FOREIGN KEY 
    (
        [fgnReportsTo]
    ) REFERENCES [Person] (
        [bmkPerson]
    ),
    CONSTRAINT [FK_Person_Party] FOREIGN KEY 
    (
        [bmkPerson]
    ) REFERENCES [Party] (
        [bmkPartyId]
    ) ON DELETE CASCADE 
) ON [PRIMARY]
GO


CREATE TABLE [UserClass] (
    [bmkUserClass] [int] NOT NULL ,
    [UserClass] [nvarchar] (50)  NOT NULL ,
    [Sort] [int] NOT NULL ,
    [Active] [bit] NOT NULL ,
    [Configured] [bit] NULL ,
    [Seq] [int] NULL ,
    [ConfigType] [varchar] (20)  NULL ,
    [CreateDate] [datetime] NOT NULL CONSTRAINT [DF__UserClass__Creat__6319B466] DEFAULT (getdate()),
    [EntityConcurrency] [timestamp] NULL ,
    [ModifyDate] [datetime] NOT NULL CONSTRAINT [DF__UserClass__Modif__7720AD13] DEFAULT (getdate()),
    CONSTRAINT [PK_zasUserClass] PRIMARY KEY  CLUSTERED 
    (
        [bmkUserClass]
    )  ON [PRIMARY] 
) ON [PRIMARY]
GO


Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 23-Jan-2006 11:47:12   

mikes wrote:

First to clarify.

I provided two examples. the UserClass is not in a heirarchy. It was a control example to show that my Concurrency was working properly for an entity outside of a heirarchy.

I also have two concurrency factories. A GenericConcurrencyFactory i wrote that works with the Userclass entity and all other entities not in a heirarchy.

I also wrote a PersonConcurrencyFactory becaues I thought maybe I had to make a more explicit check. This factory does not work either with the PersonEntity.

Sorry for the confusion.

Thanks.

Still: Query: UPDATE [dbo].[Person] SET [FirstName]=@FirstName,[LastName]=@LastName WHERE ( ( [dbo].[Person].[bmkPerson] = @BmkPerson1)) is a query which is very odd, as it has to have a FROM clause with a join with party. I'll try to reproduce it here with your specific DDL.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 23-Jan-2006 12:31:05   

I can reproduce a missing filter, and I'm looking into that. Though your setup isn't correct either. PLease read on:

Insert a new Person entity into the db and do:


select * from person
select * from party

Pay special attention to the two EntityConcurrency fields.

They don't have the same value. This is logical, because a Timestamp field doesnt receive a value from the outside, it's set by sqlserver. As you override EntityConcurrency in Person, it needs to get the value from the supertype (and in the entity object it will), but in the db, it won't, because the insert in Person will insert a different value in EntityConcurrency than in Party.

Ok, so in my example, after I did an insert, I got: Party.EntityConcurrency: 0x0000000000006DCE Person.EntityConcurrency: 0x0000000000006DCF

Though what happens when I fetch it? As the entity has just 1 EntityConcurrency field, the value will become... Person's value. (supertype fields are at the front, subtype fields are placed after that, fields are read from front to back, first the field is set to Party's version, then to Person's version.). This thus means that filters on Party never succeed, as that row has a different timestamp.

You can work around this by moving the Modified date field to the supertype, Party and by not overriding EntityConcurrency in the subtypes, as they already inherit it from the supertype. Every change on an entity has to set the modified field in an entity, which then would automatically set the entityconcurrency field in the supertype table.

I'll now see what's causing the absense of the filter.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 23-Jan-2006 12:53:52   

Ok, the update of a multi-table entity has the following philosophy: - apply the restriction filter on the first update query generated - if that one fails, all others fail as well, as everything is ran inside a transaction (automatically created if no transaction is available)

What's the problem with the code currently implement is that it always generates queries for all targets of an entity (in this case 2 tables), but if the update is in the subtype fields (which is always the case here), the first query is a no-op, but it doesn't check that, and the second query generated, which is the actual query being executed, doesn't have the filter applied.

The value compared with the timestamp is the value in the leaf of the hierarchy. So if you have a field in Party which isn't overriden in Person, updates to Party will always fail if you use this concurrency factory as it will compare with the value in Person, as there's just 1 EntityConcurrency field in the entity.

I'll fix the issue with the filter, and I'll upload a hotfix for this later today.

(Checking my unittests, they update fields in the base entity, hence I missed this one)

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 23-Jan-2006 13:30:28   

Fixed in next build: 01232006

Frans Bouma | Lead developer LLBLGen Pro
mikes
User
Posts: 14
Joined: 20-Sep-2005
# Posted on: 24-Jan-2006 19:40:10   

Thanks so much for the help.

I understand everything you are saying. I'll get the latest hot fix and try it out.

Thanks again! You guys are great!

Mike

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39930
Joined: 17-Aug-2003
# Posted on: 24-Jan-2006 20:25:59   

mikes wrote:

Thanks so much for the help.

I understand everything you are saying. I'll get the latest hot fix and try it out.

Thanks again! You guys are great!

Mike

Let me upload it first, Mike simple_smile Just a sec. In all the v2.0 code writing hectic I forgot to upload the patch!

(edit): uploaded!

Frans Bouma | Lead developer LLBLGen Pro