diff --git a/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs
new file mode 100644
index 00000000000..cd0db9a6f14
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs
@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using System.Reflection;
+using NHibernate.Transform;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH3957
+{
+ [TestFixture]
+ public class ResultTransformerEqualityFixture
+ {
+ ///
+ /// Allows to simulate a hashcode collision. Issue would be unpractical to test otherwise.
+ /// Hashcode collision must be supported for avoiding unexpected and hard to reproduce failures.
+ ///
+ private void TweakHashcode(System.Type transformerToTweak, object hasher)
+ {
+ var hasherTargetField = transformerToTweak.GetField("Hasher", BindingFlags.Static | BindingFlags.NonPublic);
+ if (!_hasherBackup.ContainsKey(transformerToTweak))
+ _hasherBackup.Add(transformerToTweak, hasherTargetField.GetValue(null));
+
+ // Though hasher is a readonly field, this works at the time of this writing. If it starts breaking and cannot be fixed,
+ // ignore those tests or throw them away.
+ hasherTargetField.SetValue(null, hasher);
+ }
+
+ private Dictionary _hasherBackup = new Dictionary();
+
+ [SetUp]
+ public void Setup()
+ {
+ var hasherForAll = typeof(AliasToEntityMapResultTransformer)
+ .GetField("Hasher", BindingFlags.Static | BindingFlags.NonPublic)
+ .GetValue(null);
+ TweakHashcode(typeof(DistinctRootEntityResultTransformer), hasherForAll);
+ TweakHashcode(typeof(PassThroughResultTransformer), hasherForAll);
+ TweakHashcode(typeof(RootEntityResultTransformer), hasherForAll);
+ TweakHashcode(typeof(ToListResultTransformer), hasherForAll);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ // Restore those types hashcode. (Avoid impacting perf of other tests, avoid second level query cache
+ // issues if it was holding cached entries (but would mean some tests have not cleaned up properly).)
+ foreach(var backup in _hasherBackup)
+ {
+ TweakHashcode(backup.Key, backup.Value);
+ }
+ }
+
+ // Non reg test case
+ [Test]
+ public void AliasToEntityMapEquality()
+ {
+ var transf1 = new AliasToEntityMapResultTransformer();
+ var transf2 = new AliasToEntityMapResultTransformer();
+
+ Assert.IsTrue(transf1.Equals(transf2));
+ Assert.IsTrue(transf2.Equals(transf1));
+ }
+
+ [Test]
+ public void AliasToEntityMapAndDistinctRootEntityInequality()
+ {
+ var transf1 = new AliasToEntityMapResultTransformer();
+ var transf2 = new DistinctRootEntityResultTransformer();
+
+ Assert.IsFalse(transf1.Equals(transf2));
+ Assert.IsFalse(transf2.Equals(transf1));
+ }
+
+ // Non reg test case
+ [Test]
+ public void DistinctRootEntityEquality()
+ {
+ var transf1 = new DistinctRootEntityResultTransformer();
+ var transf2 = new DistinctRootEntityResultTransformer();
+
+ Assert.IsTrue(transf1.Equals(transf2));
+ Assert.IsTrue(transf2.Equals(transf1));
+ }
+
+ // Non reg test case
+ [Test]
+ public void PassThroughEquality()
+ {
+ var transf1 = new PassThroughResultTransformer();
+ var transf2 = new PassThroughResultTransformer();
+
+ Assert.IsTrue(transf1.Equals(transf2));
+ Assert.IsTrue(transf2.Equals(transf1));
+ }
+
+ [Test]
+ public void PassThroughAndRootEntityInequality()
+ {
+ var transf1 = new PassThroughResultTransformer();
+ var transf2 = new RootEntityResultTransformer();
+
+ Assert.IsFalse(transf1.Equals(transf2));
+ Assert.IsFalse(transf2.Equals(transf1));
+ }
+
+ // Non reg test case
+ [Test]
+ public void RootEntityEquality()
+ {
+ var transf1 = new RootEntityResultTransformer();
+ var transf2 = new RootEntityResultTransformer();
+
+ Assert.IsTrue(transf1.Equals(transf2));
+ Assert.IsTrue(transf2.Equals(transf1));
+ }
+
+ [Test]
+ public void RootEntityAndToListInequality()
+ {
+ var transf1 = new RootEntityResultTransformer();
+ var transf2 = new ToListResultTransformer();
+
+ Assert.IsFalse(transf1.Equals(transf2));
+ Assert.IsFalse(transf2.Equals(transf1));
+ }
+
+ // Non reg test case
+ [Test]
+ public void ToListEquality()
+ {
+ var transf1 = new ToListResultTransformer();
+ var transf2 = new ToListResultTransformer();
+
+ Assert.IsTrue(transf1.Equals(transf2));
+ Assert.IsTrue(transf2.Equals(transf1));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj
index b60144583fe..faa4c8486b9 100644
--- a/src/NHibernate.Test/NHibernate.Test.csproj
+++ b/src/NHibernate.Test/NHibernate.Test.csproj
@@ -940,6 +940,7 @@
+
diff --git a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs
index d9fceb20077..3fe474856fe 100644
--- a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs
+++ b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs
@@ -30,18 +30,20 @@ public override IList TransformList(IList collection)
}
- public override bool IsTransformedValueATupleElement(String[] aliases, int tupleLength)
+ public override bool IsTransformedValueATupleElement(string[] aliases, int tupleLength)
{
return false;
}
public override bool Equals(object obj)
{
- if (obj == null)
+ if (obj == null || obj.GetHashCode() != Hasher.GetHashCode())
{
return false;
}
- return obj.GetHashCode() == Hasher.GetHashCode();
+ // NH-3957: do not rely on hashcode alone.
+ // Must be the exact same type
+ return obj.GetType() == typeof(AliasToEntityMapResultTransformer);
}
public override int GetHashCode()
diff --git a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs
index 8167ff95d64..24f4ebac408 100644
--- a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs
+++ b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs
@@ -78,11 +78,13 @@ public bool IsTransformedValueATupleElement(String[] aliases, int tupleLength)
public override bool Equals(object obj)
{
- if (obj == null)
+ if (obj == null || obj.GetHashCode() != Hasher.GetHashCode())
{
return false;
}
- return obj.GetHashCode() == Hasher.GetHashCode();
+ // NH-3957: do not rely on hashcode alone.
+ // Must be the exact same type
+ return obj.GetType() == typeof(DistinctRootEntityResultTransformer);
}
public override int GetHashCode()
diff --git a/src/NHibernate/Transform/PassThroughResultTransformer.cs b/src/NHibernate/Transform/PassThroughResultTransformer.cs
index 37715b387c3..0a116b51aff 100644
--- a/src/NHibernate/Transform/PassThroughResultTransformer.cs
+++ b/src/NHibernate/Transform/PassThroughResultTransformer.cs
@@ -61,11 +61,13 @@ internal object[] UntransformToTuple(object transformed, bool isSingleResult)
public override bool Equals(object obj)
{
- if (obj == null)
+ if (obj == null || obj.GetHashCode() != Hasher.GetHashCode())
{
return false;
}
- return obj.GetHashCode() == Hasher.GetHashCode();
+ // NH-3957: do not rely on hashcode alone.
+ // Must be the exact same type
+ return obj.GetType() == typeof(PassThroughResultTransformer);
}
public override int GetHashCode()
diff --git a/src/NHibernate/Transform/RootEntityResultTransformer.cs b/src/NHibernate/Transform/RootEntityResultTransformer.cs
index 0de6bf502c5..b8468ebbc0c 100644
--- a/src/NHibernate/Transform/RootEntityResultTransformer.cs
+++ b/src/NHibernate/Transform/RootEntityResultTransformer.cs
@@ -43,11 +43,13 @@ public bool[] IncludeInTransform(String[] aliases, int tupleLength)
public override bool Equals(object obj)
{
- if (obj == null)
+ if (obj == null || obj.GetHashCode() != Hasher.GetHashCode())
{
return false;
}
- return obj.GetHashCode() == Hasher.GetHashCode();
+ // NH-3957: do not rely on hashcode alone.
+ // Must be the exact same type
+ return obj.GetType() == typeof(RootEntityResultTransformer);
}
public override int GetHashCode()
diff --git a/src/NHibernate/Transform/ToListResultTransformer.cs b/src/NHibernate/Transform/ToListResultTransformer.cs
index 137f330b30d..e6ccc1977bd 100644
--- a/src/NHibernate/Transform/ToListResultTransformer.cs
+++ b/src/NHibernate/Transform/ToListResultTransformer.cs
@@ -25,11 +25,13 @@ public IList TransformList(IList list)
public override bool Equals(object obj)
{
- if (obj == null)
+ if (obj == null || obj.GetHashCode() != Hasher.GetHashCode())
{
return false;
}
- return obj.GetHashCode() == Hasher.GetHashCode();
+ // NH-3957: do not rely on hashcode alone.
+ // Must be the exact same type
+ return obj.GetType() == typeof(ToListResultTransformer);
}
public override int GetHashCode()