VerifyTheMappings() retrieving proxy, test fails

MylesRip's Avatar

MylesRip

12 May, 2010 10:56 PM via web

If ClassA has a property of type ClassB and I want to verify the mapping, I first create an instance of ClassB then I try to use it with PersistenceSpecification like this:

ClassB classB = new ClassB("blah");
new PersistenceSpecification<ClassA>(session)
    .CheckProperty(x => x.ClassB, classB)
    .VerifyTheMappings();

When I run the test in NUnit, I get the following error:

System.ApplicationException : For property 'ClassB' expected 'MyNamespace.ClassB' of type 'MyNamespace.ClassB' but got 'ClassBProxyf24bc4...' of type 'MyNamespace.ClassB'

I also tried using "CheckReference" instead of "CheckProperty", but I got the same results.

What am I missing here?

  1. Support Staff 2 Posted by James Gregory on 12 May, 2010 11:22 PM

    James Gregory's Avatar

    Have you overridden Equals and GetHashCode in your entity? That's a best practice with NHibernate for exactly this reason.

  2. 3 Posted by MylesRip on 12 May, 2010 11:40 PM

    MylesRip's Avatar

    For ClassA or ClassB?

    Actually, I haven't overridden "Equals" or "GetHashCode" in either of these entities. I thought that was only necessary for Value Objects, not true entities in which the Id should be used for comparisons. But I'm just as new to NHibernate as I am to Fluent NHibernate so what do I know...

    Should I be overriding these methods for every single class in my domain model? If so, NHibernate's needs are starting to become quite intrusive into my POCO classes. Hmm.

  3. Support Staff 4 Posted by James Gregory on 13 May, 2010 12:21 AM

    James Gregory's Avatar

    There's plenty of info on this on the web, and in the NHibernate docs too. See: Implementing Equals and GetHashCode.

    Equality based on the Id is risky. It's always a good idea to implement your own equality, based on your own criteria.

    For (simplified) example:

    // load entity with id == 1, and change it's name
    var entity = session.Load<Person>(1);
    entity.Name = "something else";
    
    // load same (unaltered) entity again
    var entity2 = session.Load<Person>(1);
    
    // what does this return? Id only equality would be true, even
    // though we've altered the entity
    entity.Equals(entity2)
    

    The short version is that you only need to really implement Equals and GetHashCode when you're going to be using proxying, because this creates a runtime subclass that can be used in the place of a real entity in lazy loading situations; if you try to compare a proxy to a regular instance, it'll yield unexpected results (and that's actually what's happening with the PersistenceSpecification).

  4. 5 Posted by MylesRip on 13 May, 2010 05:23 PM

    MylesRip's Avatar

    Ah, yes I remember reading that, but it didn't apply to what I was doing at the time so it didn't stick too well. I've now spent some time reading through a couple of articles on the topic.

    One thing is still not quite clear in my mind. Obviously there are different concepts of "equals" that come into play.

    The default implementation is that two objects are equal only if they are the same instance. That can work within a single session.

    A second definition is that two objects are equal if they both refer to the same entity, even if they are different instances. (They both represent the same row in the database.)

    A third definition of "equals" goes further than the second and would additionally insist that all properties in both instances must have the same values.

    The question that comes up in my mind, then, is this. What definition of "equals" should the overridden "Equals()" support. From what I've seen so far, it seems that the second definition is what most writers are trying to support. That is, you want Equals to tell you whether the objects represent the same entity, not whether one of them is dirty. The example you gave above appears to assume the third definition.

    To clarify what I mean:

    By the first definition, in your example above, entity and entity2 are not equal because they are different instances. Using the second definition, they are equal because they both represent the same row in the database, even though one of them has been modified. With the third definiton, they are not equal because one of the values doesn't match.

  5. Support Staff 6 Posted by James Gregory on 14 May, 2010 03:03 AM

    James Gregory's Avatar

    That's what I was getting at with "It's always a good idea to implement your own equality, based on your own criteria". It entirely depends on your application's requirements as to what equality strategy you take, there's not really a right answer to it.

  6. 7 Posted by MylesRip on 25 May, 2010 03:58 PM

    MylesRip's Avatar

    I'm now running into a closely-related issue that doesn't seem to be fixed by implementing Equals() and GetHashCode().

    In this case, the reference is defined as a reference to the superclass (which is abstract, but is included using "IncludeBase") and the instance that is passed into CheckReference() is a subclass.

    I end up with a message saying "... expected 'Subclass' of type 'Subclass' but got 'SuperclassProxy8b095... of type 'Superclass'."

    I have both Equals() and GetHashCode() overridden in the superclass. I also tried overriding these again in the subclass, but still got the same message. The implementation of these methods should work for any objects that inherit from the superclass. The subclass instance has not been persisted prior to the call to VerifyTheMappings, but is included in the cascading (SaveUpdate) from the entity being tested by the PersistenceSpecification.

  7. 8 Posted by MylesRip on 25 May, 2010 05:32 PM

    MylesRip's Avatar

    I found the problem... I manually saved the item, flushed and cleared the session, then read the object back and tested it. I got the same problem so I stepped through to see what was going on.

    Defining equality in my situation is pretty tricky. I don't want to use an object's Id because of the issues that is supposed to create with adding unpersisted objects to collections. So I look for "business keys" instead. Unfortunately, several classes in the domain do not have properties that are unique. It's their combination of relationships to other objects that make them unique. Because of this, in order to implement Equals(), I had to check to see if both instances were related to the same objects. One of these referenced objects has a similar situation and needed to check its relationships. To make a long story short... (maybe it's too late for that ;-) ) Equals() for the entity being tested returned false because a referenced object referenced a third object that didn't have Equals() overridden.

    Yuck. I don't like having to pull in several other objects just to confirm that two objects are equal. One solution, I suppose, would be to generate a UUID clientside as soon as an object is created. I don't really care for that solution either, but maybe it's the best one available.

    By the way, in my testing I noticed that after overriding Equals() and GetHashCode() for these entities, Equals() will return "true" in situations where the "==" operator returns "false". Should the "==" and "!=" operators always be overridden when Equals() is overridden??

    Suggestions?

  8. 9 Posted by MylesRip on 25 May, 2010 05:54 PM

    MylesRip's Avatar

    Once again, I found the answer to one of my questions above. Microsoft's guidelines for overriding Equals() and the "==" operator can be found (http://msdn.microsoft.com/en-us/library/ms173147(VS.80).aspx). These recommend that "==" should be used for reference equality and Equals() should be used for value equality. Based on their advice, the "==" operator should only be overridden for immutable objects.

    I'm still interested in comments or suggestions on the other question above. Thanks!

Reply to this discussion

Preview Comments are parsed with Markdown. Help with syntax

Attached Files

    You can attach files up to 10MB

    What is two plus two?