[FIXED] Concurrency (Timestamp) with UnitOfWork2

Posts   
 
    
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 14:24:37   

Frans,

I have discovered that UnitOfWork2 does not throw ConcurrencyViolation exceptions... Is this by design... or have I missed something?

Test1 throws an exception as expected, but Test2 does not... The only difference is the UOW replaces the save in Test2.

Is this a bug?

Marcus

[Test]
public void Test1()
{
    FolderTypeEntity instance1 = new FolderTypeEntity();
    instance1.FolderTypeUID = Guid.NewGuid();
    instance1.IconUID = Guid.NewGuid();
    instance1.DisplayName = "Name";
    instance1.MetadataTemplate = "Metadata";
    instance1.IsActive = true;

    DataAccessAdapter adapter1 = new DataAccessAdapter();
    DataAccessAdapter adapter2 = new DataAccessAdapter();
    try
    {
        adapter1.SaveEntity(instance1, true);
        
        FolderTypeEntity instance2 = new FolderTypeEntity(instance1.FolderTypeID);
        adapter2.FetchEntity(instance2);
        instance2.DisplayName = "New Name";
        adapter2.SaveEntity(instance2);

        instance1.DisplayName = "Another New Name";
        instance1.ConcurrencyPredicateFactoryToUse = new FolderTypeConcurrencyPredicateFactory();
        adapter1.SaveEntity(instance1);
    }
    finally
    {
        adapter1.Dispose();
        adapter2.Dispose();
    }
}

[Test]
public void Test2()
{
    FolderTypeEntity instance1 = new FolderTypeEntity();
    instance1.FolderTypeUID = Guid.NewGuid();
    instance1.IconUID = Guid.NewGuid();
    instance1.DisplayName = "Name";
    instance1.MetadataTemplate = "Metadata";
    instance1.IsActive = true;

    DataAccessAdapter adapter1 = new DataAccessAdapter();
    DataAccessAdapter adapter2 = new DataAccessAdapter();
    try
    {
        adapter1.SaveEntity(instance1, true);
        
        FolderTypeEntity instance2 = new FolderTypeEntity(instance1.FolderTypeID);
        adapter2.FetchEntity(instance2);
        instance2.DisplayName = "New Name";
        adapter2.SaveEntity(instance2);

        instance1.DisplayName = "Another New Name";
        instance1.ConcurrencyPredicateFactoryToUse = new FolderTypeConcurrencyPredicateFactory();

        UnitOfWork2 uow = new UnitOfWork2();
        uow.AddForSave(instance1);
        uow.Commit(adapter1, true);
    }
    finally
    {
        adapter1.Dispose();
        adapter2.Dispose();
    }
}

public class FolderTypeConcurrencyPredicateFactory : IConcurrencyPredicateFactory
{
    public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
    {
        IPredicateExpression toReturn = new PredicateExpression();
        FolderTypeEntity folderTypeEntity = (FolderTypeEntity)containingEntity;

        switch (predicateTypeToCreate)
        {
            case ConcurrencyPredicateType.Delete:
                toReturn.Add(PredicateFactory.CompareValue(FolderTypeFieldIndex.Timestamp,
                                                            ComparisonOperator.Equal,
                                                            folderTypeEntity.Fields[(int)FolderTypeFieldIndex.Timestamp].CurrentValue));
                break;
            case ConcurrencyPredicateType.Save:
                // only for updates
                toReturn.Add(PredicateFactory.CompareValue(FolderTypeFieldIndex.Timestamp,
                                                            ComparisonOperator.Equal,
                                                            folderTypeEntity.Fields[(int)FolderTypeFieldIndex.Timestamp].CurrentValue));
                break;
        }
        
        return toReturn;
    }
}
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 15:04:56   

UoW2.Commit() executes the code in a try/catch and the catch clause rolls back the transaction and throws whatever was caught, so the exception should bubble up, if it occurs.

However I think there is something with the savecode in Commit that isn't right. Just a sec.

(edit) yep, it skips the concurencypredicate expression retrieval as it calls into the savemethod which accepts a restriction, but that's assuming the restriction is retrieved from for example the concurrencypredicatefactory (as all save overloads with less parameters do so)

Fixing...

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 15:15:11   

Otis wrote:

Fixing...

So it's not me, Great! simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 15:37:08   

Fix is mailed. Please let me know if this fixes your particular situation.

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 17:15:41   

Otis wrote:

Fix is mailed. Please let me know if this fixes your particular situation.

Bloody hell... rage a Dell engineer arrived to replace the fan in my power supply. I told him to leave it with me and I would replace it myself, but he insisted on taking the faulty fan back with him.

2 hours later, I have finally got my machine working again. What an idiot, he pulled out every cable on the motherboard!!! frowning And didn't put everything back correctly.

Sorry for the delay in getting back to you... simple_smile It looks like the patch worked.

Well done again! smile

My Unit Tests indicate the patch is working, but my code is broken, so I'll conclude that I have another bug (in my code this time). disappointed

Cheers,

Marcus

[EDIT] I think there's still a problem...

Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 17:22:58   

Yes... there is still a problem... simple_smile

Did you forget about AddForDelete? smile

Marcus

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 17:26:19   

Marcus wrote:

Yes... there is still a problem... simple_smile

Did you forget about AddForDelete? smile

Marcus

No I didn't. I did add the additional logic there as well. Let me check the code carefully.

(edit) nope, should work.

(edit2) wait, there is a problem still I think. When both the restriction passed in (default null) is null and tehre is no concurrencypredicate expression. Let me re-fix this.

(and what a dork that dell guy... )

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 17:31:42   

No worries.. I had built a new test using AddForDelete which failed... I'll re-test on the new build when it becomes available. Cheers. simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 17:31:55   

Ok I'll mail you a new one, I passed back an empty PredicateCollection in some situations.

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 17:41:21   

mmmm disappointed

The test is still not passing...

[Test]
[ExpectedException(typeof(ORMConcurrencyException))]
public void Test3()
{
    FolderTypeEntity instance1 = new FolderTypeEntity();
    instance1.FolderTypeUID = Guid.NewGuid();
    instance1.IconUID = Guid.NewGuid();
    instance1.DisplayName = "Name";
    instance1.MetadataTemplate = "Metadata";
    instance1.IsActive = true;

    DataAccessAdapter adapter1 = new DataAccessAdapter();
    DataAccessAdapter adapter2 = new DataAccessAdapter();
    try
    {
        adapter1.SaveEntity(instance1, true);

        FolderTypeEntity instance2 = new FolderTypeEntity(instance1.FolderTypeID);
        adapter2.FetchEntity(instance2);
        instance2.DisplayName = "New Name";
        adapter2.SaveEntity(instance2);

        instance1.DisplayName = "Another New Name";
        instance1.ConcurrencyPredicateFactoryToUse = new FolderTypeConcurrencyPredicateFactory();

        UnitOfWork2 uow = new UnitOfWork2();
        uow.AddForDelete(instance1);
        uow.Commit(adapter1, true);
    }
    finally
    {
        adapter1.Dispose();
        adapter2.Dispose();
    }
}
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 17:43:08   

I'll try to mold your code into the code you used for the previous bug, I still have it here, to see what happens.

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 17:46:53   

Otis wrote:

I'll try to mold your code into the code you used for the previous bug, I still have it here, to see what happens.

Cool.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 17:58:08   

The code works, the ConcurrencyPredicateFactory is not correct I think.

In the DB I have FolderType with ID 16 and Timestamp 0x371B. The query (which does have the predicate produced by the factory, so that works) filters on ID = 16 and 0x371A. So clearly there is something wrong, I'm not sure where...

(edit) AH! simple_smile

You save instance1 again with instance2, the new timestamp value is in instance2, but not in instance1, which still has the timestamp value of the time THAT one was saved. So that's why the test doesn't work.

I now see I have another error in the UoW: if a save/delete fails, it should throw an ORMConcurrencyException, which is never done. So even though the test wasn't correctly implemented, my code was neither simple_smile Fixing round 3!! (this issue only pops up with non-recursive saves in a unitofwork. Recursive saves of course cause this exception already)

(edit2) Oh boy, am I captain obvious! of course that was the intention of the test sunglasses flushed ...

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 18:15:24   

Otis wrote:

You save instance1 again with instance2, the new timestamp value is in instance2, but not in instance1, which still has the timestamp value of the time THAT one was saved. So that's why the test doesn't work.

Save Instance1 with refetch to get the Timestamp A Save Instance2 which get a NEW Timestamp, called this Timstamp B Delete Instance1 (which still has Timestamp A) LLBLGen should throw a ORMConcurrencyError

The test should pass because it expects this exception with [ExpectedException(typeof(ORMConcurrencyException))] attribute.

I think the test is okay... stuck_out_tongue_winking_eye

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 18:20:52   

Fix is on its way! simple_smile

(yeah I was so focussed on why the delete failed, that I forgot that that was the purpose smile )

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 11-Jan-2005 18:29:13   

Okay, now we're talking... All test pass now with Green lights including my app! smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 11-Jan-2005 18:43:50   

Marcus wrote:

Okay, now we're talking... All test pass now with Green lights including my app! smile

great! Here too, so I'll proceed with what I was doing when you posted: creating a new full installer simple_smile

Frans Bouma | Lead developer LLBLGen Pro