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;