diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2856/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2856/Fixture.cs new file mode 100644 index 00000000000..4c71f84e7d4 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2856/Fixture.cs @@ -0,0 +1,55 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2856 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new Entity {Name = "Company", Phone = new PhoneNumber("745-555-1234"),}); + s.Save(new Entity {Name = "Bob", Phone = new PhoneNumber("745-555-1234", "x123"),}); + s.Save(new Entity {Name = "Jane", Phone = new PhoneNumber("745-555-1235"),}); + s.Flush(); + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Entity"); + s.Flush(); + t.Commit(); + } + } + + [Test] + public async Task CanDistinctOnCompositeUserTypeAsync() + { + using (var s = OpenSession()) + { + var numbers = await (s.Query().Select(x => x.Phone.Ext).Distinct().ToListAsync()); + Assert.That(numbers.Count, Is.EqualTo(2)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2856/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2856/Entity.cs new file mode 100644 index 00000000000..b8e6854b12f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2856/Entity.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.GH2856 +{ + public class Entity + { + public int? Id { get; set; } + public string Name { get; set; } + public PhoneNumber Phone { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2856/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2856/Fixture.cs new file mode 100644 index 00000000000..4e4bd9ff593 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2856/Fixture.cs @@ -0,0 +1,43 @@ +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2856 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new Entity {Name = "Company", Phone = new PhoneNumber("745-555-1234"),}); + s.Save(new Entity {Name = "Bob", Phone = new PhoneNumber("745-555-1234", "x123"),}); + s.Save(new Entity {Name = "Jane", Phone = new PhoneNumber("745-555-1235"),}); + s.Flush(); + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Entity"); + s.Flush(); + t.Commit(); + } + } + + [Test] + public void CanDistinctOnCompositeUserType() + { + using (var s = OpenSession()) + { + var numbers = s.Query().Select(x => x.Phone.Ext).Distinct().ToList(); + Assert.That(numbers.Count, Is.EqualTo(2)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2856/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2856/Mappings.hbm.xml new file mode 100644 index 00000000000..74d487991b1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2856/Mappings.hbm.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumber.cs b/src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumber.cs new file mode 100644 index 00000000000..23a10fed9c0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumber.cs @@ -0,0 +1,15 @@ +namespace NHibernate.Test.NHSpecificTest.GH2856 +{ + public class PhoneNumber + { + public PhoneNumber(string number, string ext = null) + { + Number = number; + Ext = ext; + } + + public string Number { get; } + + public string Ext { get; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumberUserType.cs b/src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumberUserType.cs new file mode 100644 index 00000000000..5f7c9d53e7d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumberUserType.cs @@ -0,0 +1,102 @@ +using System; +using System.Data.Common; +using NHibernate.Engine; +using NHibernate.Type; +using NHibernate.UserTypes; + +namespace NHibernate.Test.NHSpecificTest.GH2856 +{ + class PhoneNumberUserType : ICompositeUserType + { + public string[] PropertyNames + { + get { return new[] {"Number", "Ext"}; } + } + + public IType[] PropertyTypes + { + get { return new IType[] {NHibernateUtil.String, NHibernateUtil.String}; } + } + + public object GetPropertyValue(object component, int property) + { + PhoneNumber phone = (PhoneNumber) component; + + switch (property) + { + case 0: return phone.Number; + case 1: return phone.Ext; + default: throw new NotImplementedException(); + } + } + + public void SetPropertyValue(object component, int property, object value) + { + throw new NotImplementedException(); + } + + public System.Type ReturnedClass + { + get { return typeof(PhoneNumber); } + } + + public new bool Equals(object x, object y) + { + if (ReferenceEquals(x, null) && ReferenceEquals(y, null)) + return true; + + if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) + return false; + + return x.Equals(y); + } + + public int GetHashCode(object x) + { + return x.GetHashCode(); + } + + public object NullSafeGet(DbDataReader dr, string[] names, ISessionImplementor session, object owner) + { + return dr.IsDBNull(dr.GetOrdinal(names[0])) + ? null + : (object) new PhoneNumber( + (string) NHibernateUtil.String.NullSafeGet(dr, names[0], session, owner), + (string) NHibernateUtil.String.NullSafeGet(dr, names[1], session, owner)); + } + + public void NullSafeSet(DbCommand cmd, object value, int index, bool[] settable, ISessionImplementor session) + { + object number = value == null ? null : ((PhoneNumber) value).Number; + object ext = value == null ? null : ((PhoneNumber) value).Ext; + + if (settable[0]) NHibernateUtil.String.NullSafeSet(cmd, number, index++, session); + if (settable[1]) NHibernateUtil.String.NullSafeSet(cmd, ext, index, session); + } + + public object DeepCopy(object value) + { + return value; + } + + public bool IsMutable + { + get { return false; } + } + + public object Disassemble(object value, ISessionImplementor session) + { + return value; + } + + public object Assemble(object cached, ISessionImplementor session, object owner) + { + return cached; + } + + public object Replace(object original, object target, ISessionImplementor session, object owner) + { + return original; + } + } +} diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index efa12b4e661..050a4d2ed22 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -367,23 +367,14 @@ private static bool TraverseMembers( break; case IAbstractComponentType componentType: currentComponentType = componentType; - if (currentEntityPersister == null) + currentType = TryGetComponentPropertyType(componentType, member.Path); + if (currentEntityPersister != null) { - // When persister is not available (q.OneToManyCompositeElement[0].Prop), try to get the type from the component - currentType = TryGetComponentPropertyType(componentType, member.Path); - } - else - { - // Concatenate the component property path in order to be able to use EntityMetamodel.GetPropertyType to retrieve the type. - // As GetPropertyType supports only components, do not concatenate when dealing with collection composite elements or elements. // q.Component.Prop member = new MemberMetadata( - $"{memberPath}.{member.Path}", + memberPath + "." + member.Path, member.ConvertType, member.HasIndexer); - - // q.Component.Prop - currentType = currentEntityPersister.EntityMetamodel.GetPropertyType(member.Path); } break;