RejectChanges and IsDirty

Posts   
 
    
Chad2
User
Posts: 8
Joined: 19-Mar-2007
# Posted on: 19-Mar-2007 01:53:29   

Hi all,

I'm not sure if RejectChanges() is the right method to accomplish this so if not please help. Well, this is my scenario.

1) We have an entity and validator.

2) We create a new entity, attach validator and persist to the database. 3) We change a field that would cause the validation to fail, attempt save and it fails. 4) We change the same field again so that when saving the validation will fail.

5) We then call RejectChanges(). 6) The entity goes back to !IsDirty.

7) BUT, the last bad value is still in the entity field ... so the entity should actually be dirty.

Am I missing something?

Some test code to explain... I'll try to attatch it as well.

namespace LSSF.CoreTest.BMTests { [TestFixture] public class Bug { private const string STR_ValidationFailed = "validation failed";

    private class UserValidator : ValidatorBase
    {
        public override void ValidateEntityBeforeSave(IEntityCore involvedEntity)
        {
            UserEntity lUserEntity = (UserEntity) involvedEntity;
            if (lUserEntity.UserName.Length > 20)
            {
                lUserEntity.SetEntityFieldError("UserName", "must be 20 chars or less", true);
                lUserEntity.SetEntityError(STR_ValidationFailed);
                throw new ORMEntityValidationException(STR_ValidationFailed, involvedEntity);
            }
        }
    }

    [Test]
    public void T010_M_ValidationError()
    {
        // UserName varchar(30), not null

        string lUserName = "N" + DateTime.Now.Ticks.ToString();

        // Create and save a new entity
        UserEntity lUserEntity = new UserEntity(Guid.NewGuid());
        lUserEntity.UserName = lUserName;
        lUserEntity.Secret = "hash";
        lUserEntity.Validator = new UserValidator();
        bool lResult = TestHelper.GetInstance().DAA.SaveEntity(lUserEntity, true);
        Assert.IsTrue(lResult);
        Assert.IsFalse(lUserEntity.IsDirty);
        Console.WriteLine("Created user : {0}", lUserEntity.UserName);

        // Validation failure on username first time
        try
        {
            lUserEntity.UserName = new string('a', 30);
            TestHelper.GetInstance().DAA.SaveEntity(lUserEntity, true);
        }
        catch (ORMEntityValidationException ex)
        {
            Assert.AreEqual(STR_ValidationFailed, ex.Message);
        }
        catch (Exception ex)
        {
            Assert.Fail("Should have gotten a ORMEntityValicationException here but was an {0} with message {1}.", ex.GetType(), ex.Message);
        }
        Assert.IsTrue(lUserEntity.IsDirty);

        // Validation failure on username the second time
        try
        {
            lUserEntity.UserName = new string('b', 30);
            TestHelper.GetInstance().DAA.SaveEntity(lUserEntity, true);
        }
        catch (ORMEntityValidationException ex)
        {
            Assert.AreEqual(STR_ValidationFailed, ex.Message);
        }
        catch (Exception ex)
        {
            Assert.Fail("Should have gotten a ORMEntityValicationException here but was an {0} with message {1}.", ex.GetType(), ex.Message);
        }
        Assert.IsTrue(lUserEntity.IsDirty);

        // Reject the changes
        lUserEntity.RejectChanges();
        Assert.IsFalse(lUserEntity.IsDirty);
        Assert.AreEqual(lUserName, lUserEntity.UserName, "But the username is still the old rejected value!");
        Console.WriteLine("Validation errors as expected, UserName back to normal.");
    }
}

}

Attachments
Filename File size Added on Approval
Bug.cs 2,690 19-Mar-2007 01:54.04 Approved
Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 19-Mar-2007 08:12:54   

Which LLBLGen Pro runtimeLibrary version are you using?

If you comment out the second SaveEntity() call (the one wich should fail), I think the RejectChanges will give you the effect the you want.

The idea is that RejectChanges rolls back the fields values to the values before the edit cycle started. And when you call SaveEntity(), you are actually ending an edit cycle, and whatever you do next is a new edit cycle.

What you may want to use is Field Data Versioning SaveFields() & RollBackFields() methods, found in the entity class. Since RejectChanges() was ment to be used with databinding purposes.

Chad2
User
Posts: 8
Joined: 19-Mar-2007
# Posted on: 19-Mar-2007 12:42:21   

The latest runtime have the same outcome. Well I guess the main problem is not the way RejectChanges() operate but rather the inconsistency between the dirty entity data and the IsDirty property.

I would much rather be using RejectChanges() than RollbackFields() since RejectChanges() uses the internal _originalValue field for rollback rather than a dictionary. Not even to mention the extra overhead if we have to use SaveFields().

Can you confirm the current behavior as a bug? And if so is there a easy workaround without having to use SaveFields() until there's a fix?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39887
Joined: 17-Aug-2003
# Posted on: 20-Mar-2007 11:58:05   

I can't deny that the behavior isn't matching what you'd expect, however there's a problem: there's no dirty state saving with that routine (IEditableObject's implementation does do that though). It's actually dead code, it's not used internally by the framework anymore. RejectChanges was added in the very beginning of the framework when I looked at how the datatable worked and I thought it would be good to have it. Though later on it became clear the datatable's rejectchanges doesn't serve a real purpose, and so doesn't this method. The thing is that you have just 1 level of versioning, which isn't really going to help. What you want is multi-level versioning, which is why SaveFields/RollbackFields was added.

The overhead of Save/RollbackFields is really minor, as for example a transaction also use SaveFields/RollbackFields to rollback to a state prior to the transaction start, so every change made during a transaction is rolled back.

I'll mark RejectChanges obsolete in v2.1, thanks for catching this. You should use SaveFields/RollbackFields as they fit your problem and were added for situations like your routine.

Frans Bouma | Lead developer LLBLGen Pro
Chad2
User
Posts: 8
Joined: 19-Mar-2007
# Posted on: 20-Mar-2007 13:20:05   

smile , actually I was only looking for single level versioning and the test routine was just to point out the issue. But no problem, time to move on and start using multiple undo levels.

Thanks guys