diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1180/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1180/FixtureByCode.cs
new file mode 100644
index 00000000000..baf5bbacdbb
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1180/FixtureByCode.cs
@@ -0,0 +1,146 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Criterion;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH1180
+{
+ using System.Threading.Tasks;
+ [KnownBug("NH-3847 (GH-1180)")]
+ [TestFixture]
+ public class ByCodeFixtureAsync : TestCaseMappingByCode
+ {
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Name, m => { m.Length(10); });
+ rc.Property(x => x.Amount, m => { m.Precision(8); m.Scale(2); });
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.CreateQuery("delete from System.Object").ExecuteUpdate();
+ transaction.Commit();
+ }
+ }
+
+ [Test]
+ public async Task StringTypesAsync()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // data
+ await (session.SaveAsync(new Entity {Name = "Alpha"}));
+ await (session.SaveAsync(new Entity {Name = "Beta"}));
+ await (session.SaveAsync(new Entity {Name = "Gamma"}));
+
+ await (transaction.CommitAsync());
+ }
+
+ // whenTrue is constant, whenFalse is property -> works even before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Not(
+ Restrictions.Like(nameof(Entity.Name), "B%")),
+ Projections.Constant("other"),
+ Projections.Property(nameof(Entity.Name)));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = await (tagCriteria.ListAsync());
+
+ Assert.That(results, Is.EquivalentTo(new[] {"other", "Beta", "other"}));
+ }
+
+ // whenTrue is property, whenFalse is constant -> fails before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Like(nameof(Entity.Name), "B%"),
+ Projections.Property(nameof(Entity.Name)),
+ Projections.Constant("other"));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = await (tagCriteria.ListAsync());
+
+ Assert.That(results, Is.EquivalentTo(new[] {"other", "Beta", "other"}));
+ }
+ }
+
+ [Test]
+ public async Task DecimalTypesAsync()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ await (session.SaveAsync(new Entity {Amount = 3.14m}));
+ await (session.SaveAsync(new Entity {Amount = 42.13m}));
+ await (session.SaveAsync(new Entity {Amount = 17.99m}));
+
+ await (transaction.CommitAsync());
+ }
+
+ // whenTrue is constant, whenFalse is property -> works even before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Not(
+ Restrictions.Ge(nameof(Entity.Amount), 20m)),
+ Projections.Constant(20m),
+ Projections.Property(nameof(Entity.Amount)));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = await (tagCriteria.ListAsync());
+
+ Assert.That(results, Is.EquivalentTo(new[] {20m, 42.13m, 20m}));
+ }
+
+ // whenTrue is property, whenFalse is constant -> fails before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Ge(nameof(Entity.Amount), 20m),
+ Projections.Property(nameof(Entity.Amount)),
+ Projections.Constant(20m));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = await (tagCriteria.ListAsync());
+
+ Assert.That(results, Is.EquivalentTo(new[] {20m, 42.13m, 20m}));
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH1180/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1180/Entity.cs
new file mode 100644
index 00000000000..bf16619beb8
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH1180/Entity.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH1180
+{
+ internal class Entity
+ {
+ public virtual Guid Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual decimal Amount { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH1180/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH1180/FixtureByCode.cs
new file mode 100644
index 00000000000..a7590840459
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH1180/FixtureByCode.cs
@@ -0,0 +1,135 @@
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Criterion;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH1180
+{
+ [KnownBug("NH-3847 (GH-1180)")]
+ [TestFixture]
+ public class ByCodeFixture : TestCaseMappingByCode
+ {
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+ rc.Property(x => x.Name, m => { m.Length(10); });
+ rc.Property(x => x.Amount, m => { m.Precision(8); m.Scale(2); });
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.CreateQuery("delete from System.Object").ExecuteUpdate();
+ transaction.Commit();
+ }
+ }
+
+ [Test]
+ public void StringTypes()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ // data
+ session.Save(new Entity {Name = "Alpha"});
+ session.Save(new Entity {Name = "Beta"});
+ session.Save(new Entity {Name = "Gamma"});
+
+ transaction.Commit();
+ }
+
+ // whenTrue is constant, whenFalse is property -> works even before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Not(
+ Restrictions.Like(nameof(Entity.Name), "B%")),
+ Projections.Constant("other"),
+ Projections.Property(nameof(Entity.Name)));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = tagCriteria.List();
+
+ Assert.That(results, Is.EquivalentTo(new[] {"other", "Beta", "other"}));
+ }
+
+ // whenTrue is property, whenFalse is constant -> fails before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Like(nameof(Entity.Name), "B%"),
+ Projections.Property(nameof(Entity.Name)),
+ Projections.Constant("other"));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = tagCriteria.List();
+
+ Assert.That(results, Is.EquivalentTo(new[] {"other", "Beta", "other"}));
+ }
+ }
+
+ [Test]
+ public void DecimalTypes()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ session.Save(new Entity {Amount = 3.14m});
+ session.Save(new Entity {Amount = 42.13m});
+ session.Save(new Entity {Amount = 17.99m});
+
+ transaction.Commit();
+ }
+
+ // whenTrue is constant, whenFalse is property -> works even before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Not(
+ Restrictions.Ge(nameof(Entity.Amount), 20m)),
+ Projections.Constant(20m),
+ Projections.Property(nameof(Entity.Amount)));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = tagCriteria.List();
+
+ Assert.That(results, Is.EquivalentTo(new[] {20m, 42.13m, 20m}));
+ }
+
+ // whenTrue is property, whenFalse is constant -> fails before the fix
+ using (var session = OpenSession())
+ {
+ ICriteria tagCriteria = session.CreateCriteria(typeof(Entity));
+
+ var conditionalProjection = Projections.Conditional(
+ Restrictions.Ge(nameof(Entity.Amount), 20m),
+ Projections.Property(nameof(Entity.Amount)),
+ Projections.Constant(20m));
+ tagCriteria.SetProjection(conditionalProjection);
+
+ // run query
+ var results = tagCriteria.List();
+
+ Assert.That(results, Is.EquivalentTo(new[] {20m, 42.13m, 20m}));
+ }
+ }
+ }
+}
diff --git a/src/NHibernate/Type/NullableType.cs b/src/NHibernate/Type/NullableType.cs
index e84b6df48da..883894ee971 100644
--- a/src/NHibernate/Type/NullableType.cs
+++ b/src/NHibernate/Type/NullableType.cs
@@ -377,6 +377,15 @@ public override int GetHashCode()
return (SqlType.GetHashCode() / 2) + (Name.GetHashCode() / 2);
}
+ ///
+ /// Provides a more descriptive string representation by reporting the properties that are important for equality.
+ /// Useful in error messages.
+ ///
+ public override string ToString()
+ {
+ return $"{base.ToString()} (SqlType: {SqlType})";
+ }
+
#endregion
}
}