简体   繁体   中英

Avoid locking an Object that's already in the session (UniqueObjectException)

I call this function on my objects when they need to be initialized again:

public virtual void Initialize()
{
    if (!HibernateSessionManager.Instance.GetSession().Contains(this)) {

        try
        {
            HibernateSessionManager.Instance.GetSession()
                             .Lock(this, NHibernate.LockMode.None);
        }
        catch (NonUniqueObjectException e) { }
    }
}

I thought I can prevent initializing something twice with checking Contains(this) , but it happens sometimes that Lock(this, NHibernate.LockMode.None) throws a NonUniqueObjectException . So far I ignore it because it works, but I'd like to know the reason and a better way to Lock my objects.

Best regards, Expecto

This most likely means you violate the Identity map somewhere. This means you have two instances of an object hanging around with the same database ID, but different referential identity.

Session.Contains will check reference equality, but Lock will throw an exception if there's anything with the same type & id already in the session, which is a much less strict comparison.

Consider the following test on the AdventureWorks database, with a (very naive and unrecommended) simple implementation of Equals & GetHashCode

 using (ISession session = SessionFactory.Factory.OpenSession())
        {
            int someId = 329;

            Person p = session.Get<Person>(someId);
            Person test = new Person() { BusinessEntityID = someId };

            Assert.IsTrue(p.Equals(test)); //your code might think the objects are equal, so you'd probably expect the next line to return true         

            Assert.IsFalse(session.Contains(test)); //But they're not the same object

            Assert.Throws<NonUniqueObjectException>(() =>
            {
                session.Lock(test, LockMode.None); //So when you ask nhibernate to track changes on both objects, it gets very confused
            });
        }

NHibernate (and I'd guess any ORM) works by tracking changes to objects. So in Get'ing Person 329, you ask NHibernate to pay attention to whatever happens to that particular instance of a Person. Let's say we change his first-name to Jaime.

Next, we get another instance of person with the same Id (in this case we just new'ed it up, but there are many insidious ways to get such an object). Imagine NHibernate would let us attach this to the session as well. We could even set the first-name of this second object to something like Robb.

When we flush the session NHibernate has no way of knowing whether the database row needs to be synched to either Robb or Jaime. So it throws the non-unique your way before that could happen.

Ideally these situations shouldn't crop up, but if you're very sure what's happening, you might want to check out session.Merge, which lets you force the tracked state to whatever happens to be merged in last (Robb in the example).

the problem was a completely different - contains checks for equality by reference if I don't override Equals() . Now it works with the code from my question!

    public override bool Equals(object obj)
    {
        if (this == obj) { 

            return true;
        } 

        if (GetType() != obj.GetType()) {

            return false;
        }

        if (Id != ((BaseObject)obj).Id)
        {

            return false;
        }

        return true;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM