From 83ef1c74c0d2b3328500a580ad5bf71b6cf71cff Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 27 Jul 2021 10:40:04 +0300 Subject: [PATCH 1/3] Fix component type retrieval in LINQ --- .../Async/NHSpecificTest/GH2856/Fixture.cs | 55 ++++++++++ .../NHSpecificTest/GH2856/Entity.cs | 9 ++ .../NHSpecificTest/GH2856/Fixture.cs | 43 ++++++++ .../NHSpecificTest/GH2856/Mappings.hbm.xml | 13 +++ .../NHSpecificTest/GH2856/PhoneNumber.cs | 15 +++ .../GH2856/PhoneNumberUserType.cs | 102 ++++++++++++++++++ src/NHibernate/Util/ExpressionsHelper.cs | 4 +- 7 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2856/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2856/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2856/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2856/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumber.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2856/PhoneNumberUserType.cs 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 221b8031266..732f37b367c 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -367,7 +367,7 @@ private static bool TraverseMembers( } else { - // Concatenate the component property path in order to be able to use EntityMetamodel.GetPropertyType to retrieve the type. + // Concatenate the component property path in order to be able to use IEntityPersister.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( @@ -376,7 +376,7 @@ private static bool TraverseMembers( member.HasIndexer); // q.Component.Prop - currentType = currentEntityPersister.EntityMetamodel.GetPropertyType(member.Path); + currentType = currentEntityPersister.GetPropertyType(member.Path); } break; From cb37ef283ea5ed2ab851aa50347a04910495df95 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 27 Jul 2021 11:28:25 +0300 Subject: [PATCH 2/3] Simplify fix --- src/NHibernate/Util/ExpressionsHelper.cs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index 732f37b367c..71bc6323ac4 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -360,25 +360,7 @@ private static bool TraverseMembers( break; case IAbstractComponentType componentType: currentComponentType = componentType; - 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 IEntityPersister.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}", - member.ConvertType, - member.HasIndexer); - - // q.Component.Prop - currentType = currentEntityPersister.GetPropertyType(member.Path); - } - + currentType = TryGetComponentPropertyType(componentType, member.Path); break; default: // q.Prop.NotMappedProp From 6c39c169088e30c4ddfa1f624a31676f91d1947e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 27 Jul 2021 11:59:07 +0300 Subject: [PATCH 3/3] Oversimplified --- src/NHibernate/Util/ExpressionsHelper.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index 71bc6323ac4..6a1874c9956 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -361,6 +361,15 @@ private static bool TraverseMembers( case IAbstractComponentType componentType: currentComponentType = componentType; currentType = TryGetComponentPropertyType(componentType, member.Path); + if (currentEntityPersister != null) + { + // q.Component.Prop + member = new MemberMetadata( + memberPath + "." + member.Path, + member.ConvertType, + member.HasIndexer); + } + break; default: // q.Prop.NotMappedProp