Skip to content

Commit 6c0fd01

Browse files
authored
Fix property type retrieval for Composite User Type in LINQ (#2877)
Fixes #2856
1 parent 7035c55 commit 6c0fd01

File tree

7 files changed

+240
-12
lines changed

7 files changed

+240
-12
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Linq;
12+
using NUnit.Framework;
13+
using NHibernate.Linq;
14+
15+
namespace NHibernate.Test.NHSpecificTest.GH2856
16+
{
17+
using System.Threading.Tasks;
18+
[TestFixture]
19+
public class FixtureAsync : BugTestCase
20+
{
21+
protected override void OnSetUp()
22+
{
23+
using (var s = Sfi.OpenSession())
24+
using (var t = s.BeginTransaction())
25+
{
26+
s.Save(new Entity {Name = "Company", Phone = new PhoneNumber("745-555-1234"),});
27+
s.Save(new Entity {Name = "Bob", Phone = new PhoneNumber("745-555-1234", "x123"),});
28+
s.Save(new Entity {Name = "Jane", Phone = new PhoneNumber("745-555-1235"),});
29+
s.Flush();
30+
t.Commit();
31+
}
32+
}
33+
34+
protected override void OnTearDown()
35+
{
36+
using (var s = Sfi.OpenSession())
37+
using (var t = s.BeginTransaction())
38+
{
39+
s.Delete("from Entity");
40+
s.Flush();
41+
t.Commit();
42+
}
43+
}
44+
45+
[Test]
46+
public async Task CanDistinctOnCompositeUserTypeAsync()
47+
{
48+
using (var s = OpenSession())
49+
{
50+
var numbers = await (s.Query<Entity>().Select(x => x.Phone.Ext).Distinct().ToListAsync());
51+
Assert.That(numbers.Count, Is.EqualTo(2));
52+
}
53+
}
54+
}
55+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NHibernate.Test.NHSpecificTest.GH2856
2+
{
3+
public class Entity
4+
{
5+
public int? Id { get; set; }
6+
public string Name { get; set; }
7+
public PhoneNumber Phone { get; set; }
8+
}
9+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Linq;
2+
using NUnit.Framework;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH2856
5+
{
6+
[TestFixture]
7+
public class Fixture : BugTestCase
8+
{
9+
protected override void OnSetUp()
10+
{
11+
using (var s = Sfi.OpenSession())
12+
using (var t = s.BeginTransaction())
13+
{
14+
s.Save(new Entity {Name = "Company", Phone = new PhoneNumber("745-555-1234"),});
15+
s.Save(new Entity {Name = "Bob", Phone = new PhoneNumber("745-555-1234", "x123"),});
16+
s.Save(new Entity {Name = "Jane", Phone = new PhoneNumber("745-555-1235"),});
17+
s.Flush();
18+
t.Commit();
19+
}
20+
}
21+
22+
protected override void OnTearDown()
23+
{
24+
using (var s = Sfi.OpenSession())
25+
using (var t = s.BeginTransaction())
26+
{
27+
s.Delete("from Entity");
28+
s.Flush();
29+
t.Commit();
30+
}
31+
}
32+
33+
[Test]
34+
public void CanDistinctOnCompositeUserType()
35+
{
36+
using (var s = OpenSession())
37+
{
38+
var numbers = s.Query<Entity>().Select(x => x.Phone.Ext).Distinct().ToList();
39+
Assert.That(numbers.Count, Is.EqualTo(2));
40+
}
41+
}
42+
}
43+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test" namespace="NHibernate.Test.NHSpecificTest.GH2856">
3+
<class name="Entity" table="a" lazy="false" optimistic-lock="dirty" dynamic-update="true">
4+
<id name="Id" column="id" unsaved-value="null">
5+
<generator class="native" />
6+
</id>
7+
<property name="Name"/>
8+
<property name="Phone" type="NHibernate.Test.NHSpecificTest.GH2856.PhoneNumberUserType, NHibernate.Test">
9+
<column name="PhoneNumber"/>
10+
<column name="PhoneExt"/>
11+
</property>
12+
</class>
13+
</hibernate-mapping>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace NHibernate.Test.NHSpecificTest.GH2856
2+
{
3+
public class PhoneNumber
4+
{
5+
public PhoneNumber(string number, string ext = null)
6+
{
7+
Number = number;
8+
Ext = ext;
9+
}
10+
11+
public string Number { get; }
12+
13+
public string Ext { get; }
14+
}
15+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Data.Common;
3+
using NHibernate.Engine;
4+
using NHibernate.Type;
5+
using NHibernate.UserTypes;
6+
7+
namespace NHibernate.Test.NHSpecificTest.GH2856
8+
{
9+
class PhoneNumberUserType : ICompositeUserType
10+
{
11+
public string[] PropertyNames
12+
{
13+
get { return new[] {"Number", "Ext"}; }
14+
}
15+
16+
public IType[] PropertyTypes
17+
{
18+
get { return new IType[] {NHibernateUtil.String, NHibernateUtil.String}; }
19+
}
20+
21+
public object GetPropertyValue(object component, int property)
22+
{
23+
PhoneNumber phone = (PhoneNumber) component;
24+
25+
switch (property)
26+
{
27+
case 0: return phone.Number;
28+
case 1: return phone.Ext;
29+
default: throw new NotImplementedException();
30+
}
31+
}
32+
33+
public void SetPropertyValue(object component, int property, object value)
34+
{
35+
throw new NotImplementedException();
36+
}
37+
38+
public System.Type ReturnedClass
39+
{
40+
get { return typeof(PhoneNumber); }
41+
}
42+
43+
public new bool Equals(object x, object y)
44+
{
45+
if (ReferenceEquals(x, null) && ReferenceEquals(y, null))
46+
return true;
47+
48+
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
49+
return false;
50+
51+
return x.Equals(y);
52+
}
53+
54+
public int GetHashCode(object x)
55+
{
56+
return x.GetHashCode();
57+
}
58+
59+
public object NullSafeGet(DbDataReader dr, string[] names, ISessionImplementor session, object owner)
60+
{
61+
return dr.IsDBNull(dr.GetOrdinal(names[0]))
62+
? null
63+
: (object) new PhoneNumber(
64+
(string) NHibernateUtil.String.NullSafeGet(dr, names[0], session, owner),
65+
(string) NHibernateUtil.String.NullSafeGet(dr, names[1], session, owner));
66+
}
67+
68+
public void NullSafeSet(DbCommand cmd, object value, int index, bool[] settable, ISessionImplementor session)
69+
{
70+
object number = value == null ? null : ((PhoneNumber) value).Number;
71+
object ext = value == null ? null : ((PhoneNumber) value).Ext;
72+
73+
if (settable[0]) NHibernateUtil.String.NullSafeSet(cmd, number, index++, session);
74+
if (settable[1]) NHibernateUtil.String.NullSafeSet(cmd, ext, index, session);
75+
}
76+
77+
public object DeepCopy(object value)
78+
{
79+
return value;
80+
}
81+
82+
public bool IsMutable
83+
{
84+
get { return false; }
85+
}
86+
87+
public object Disassemble(object value, ISessionImplementor session)
88+
{
89+
return value;
90+
}
91+
92+
public object Assemble(object cached, ISessionImplementor session, object owner)
93+
{
94+
return cached;
95+
}
96+
97+
public object Replace(object original, object target, ISessionImplementor session, object owner)
98+
{
99+
return original;
100+
}
101+
}
102+
}

src/NHibernate/Util/ExpressionsHelper.cs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -367,23 +367,14 @@ private static bool TraverseMembers(
367367
break;
368368
case IAbstractComponentType componentType:
369369
currentComponentType = componentType;
370-
if (currentEntityPersister == null)
370+
currentType = TryGetComponentPropertyType(componentType, member.Path);
371+
if (currentEntityPersister != null)
371372
{
372-
// When persister is not available (q.OneToManyCompositeElement[0].Prop), try to get the type from the component
373-
currentType = TryGetComponentPropertyType(componentType, member.Path);
374-
}
375-
else
376-
{
377-
// Concatenate the component property path in order to be able to use EntityMetamodel.GetPropertyType to retrieve the type.
378-
// As GetPropertyType supports only components, do not concatenate when dealing with collection composite elements or elements.
379373
// q.Component.Prop
380374
member = new MemberMetadata(
381-
$"{memberPath}.{member.Path}",
375+
memberPath + "." + member.Path,
382376
member.ConvertType,
383377
member.HasIndexer);
384-
385-
// q.Component.Prop
386-
currentType = currentEntityPersister.EntityMetamodel.GetPropertyType(member.Path);
387378
}
388379

389380
break;

0 commit comments

Comments
 (0)