nhibernate-core
nhibernate-core copied to clipboard
Equals is ignored for Components that contain collection
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:
-
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?
-
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?
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 can you submit a test case?
@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.
You have incorrect GetHashCode
generation. It assumes hash code of an instance of a set, instead of it's elements.
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);