nhibernate-core icon indicating copy to clipboard operation
nhibernate-core copied to clipboard

Equals is ignored for Components that contain collection

Open DmitryMak opened this issue 9 months ago • 5 comments

NHibernate ignores Equals implementation on Components when they contain a collection. As a result, it issues an unnecessary DELETE followed by multiple INSERTs. It basically recreates all rows that were backing the collection even though the collection has not changed.

Example:


<class name="Person">
    <component name="CreditInfo">
        <bag name="OldCards" cascade="all" >
            <key column="PersonId" />
                ...


class Person {
    public CreditInfo CreditInfo;
}

public class CreditInfo {
    public readonly ICollection<String> OldCards;

    public CreditInfo(IEnumerable<String> oldCards) {        
        OldCards = new List<String>(oldCards);
    }
    
    public override Boolean Equals(Object obj) {

        var other = (CreditInfo)obj;
        return OldCards.SequenceEqual(other.OldCards);  <-- No changes detected by Equals!
    }
}

 
person.CreditInfo = new CreditInfo(person.CreditInfo.OldCards);  <-- effectively cloned

// will DELETE all card rows and INSERT all of them again even though CreditInfo did not change

I think there are 2 counterintuitive behaviors here:

  1. Why is NHibernate ignoring CreditInfo.Equals? The method tells it that the component (value type) has not changed. A very common immutable Value Object scenario. Why does it have to go 'inside' the component?

  2. Replacing NHibernate collection (backing OldCards) with a new collection with the same items seem to make NHibernate delete and reinsert all rows. Is there a way to implement value equality on collections, think SequenceEquals?

DmitryMak avatar May 02 '24 18:05 DmitryMak

Essentially I want to implement a small immutable collection that is compared by value (SequenceEquals). It looks like my only option is to serialize the collection into a string and store it as a column.

DmitryMak avatar May 02 '24 19:05 DmitryMak

@DmitryMak can you submit a test case?

hazzik avatar May 02 '24 23:05 hazzik

@hazzik I submitted a PR with a test that explains what I meant. Though I'm not sure how to write an assertion that fails when an unexpected DML is issued.

DmitryMak avatar May 03 '24 05:05 DmitryMak

You have incorrect GetHashCode generation. It assumes hash code of an instance of a set, instead of it's elements.

hazzik avatar May 03 '24 05:05 hazzik

I'm not sure how to write an assertion that fails when an unexpected DML is issued.

Something like this:

using var spy = new SqlLogSpy();
t.Commit();
Assert.That(spy.GetWholeLog(), Is.Empty);

hazzik avatar May 03 '24 05:05 hazzik