Skip to content

Commit d68d402

Browse files
bahusoidhazzik
andauthored
Use entities prepared by Loader in hql select projections (#2082)
Co-authored-by: Alexander Zaytsev <[email protected]>
1 parent 07ffb61 commit d68d402

File tree

8 files changed

+275
-18
lines changed

8 files changed

+275
-18
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 NHibernate.Cfg.MappingSchema;
13+
using NHibernate.Mapping.ByCode;
14+
using NUnit.Framework;
15+
using NHibernate.Linq;
16+
17+
namespace NHibernate.Test.NHSpecificTest.GH2064
18+
{
19+
using System.Threading.Tasks;
20+
[TestFixture]
21+
public class OneToOneSelectProjectionFixtureAsync : TestCaseMappingByCode
22+
{
23+
protected override HbmMapping GetMappings()
24+
{
25+
var mapper = new ModelMapper();
26+
mapper.Class<OneToOneEntity>(
27+
rc =>
28+
{
29+
rc.Id(e => e.Id, m => m.Generator(Generators.Assigned));
30+
rc.Property(e => e.Name);
31+
});
32+
33+
mapper.Class<ParentEntity>(
34+
rc =>
35+
{
36+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
37+
rc.Property(e => e.Name);
38+
rc.OneToOne(e => e.OneToOne, m => { });
39+
});
40+
41+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
42+
}
43+
44+
protected override void OnSetUp()
45+
{
46+
using (var session = OpenSession())
47+
using (var transaction = session.BeginTransaction())
48+
{
49+
var nullableOwner = new ParentEntity() {Name = "Owner",};
50+
var oneToOne = new OneToOneEntity() {Name = "OneToOne"};
51+
nullableOwner.OneToOne = oneToOne;
52+
session.Save(nullableOwner);
53+
oneToOne.Id = nullableOwner.Id;
54+
session.Save(oneToOne);
55+
session.Flush();
56+
57+
transaction.Commit();
58+
}
59+
}
60+
61+
protected override void OnTearDown()
62+
{
63+
using (var session = OpenSession())
64+
using (var transaction = session.BeginTransaction())
65+
{
66+
// The HQL delete does all the job inside the database without loading the entities, but it does
67+
// not handle delete order for avoiding violating constraints if any. Use
68+
// session.Delete("from System.Object");
69+
// instead if in need of having NHibernate ordering the deletes, but this will cause
70+
// loading the entities in the session.
71+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
72+
73+
transaction.Commit();
74+
}
75+
}
76+
77+
[Test]
78+
public async Task QueryOneToOneAsync()
79+
{
80+
using (var session = OpenSession())
81+
{
82+
var entity =
83+
await (session
84+
.Query<ParentEntity>()
85+
.FirstOrDefaultAsync());
86+
Assert.That(entity.OneToOne, Is.Not.Null);
87+
}
88+
}
89+
90+
[Test]
91+
public async Task QueryOneToOneProjectionAsync()
92+
{
93+
using (var session = OpenSession())
94+
{
95+
var entity =
96+
await (session
97+
.Query<ParentEntity>()
98+
.Select(
99+
x => new
100+
{
101+
x.Id,
102+
SubType = new {x.OneToOne, x.Name},
103+
SubType2 = new
104+
{
105+
x.Id,
106+
x.OneToOne,
107+
SubType3 = new {x.Id, x.OneToOne}
108+
},
109+
x.OneToOne
110+
}).FirstOrDefaultAsync());
111+
Assert.That(entity.OneToOne, Is.Not.Null);
112+
Assert.That(entity.SubType.OneToOne, Is.Not.Null);
113+
Assert.That(entity.SubType2.OneToOne, Is.Not.Null);
114+
Assert.That(entity.SubType2.SubType3.OneToOne, Is.Not.Null);
115+
}
116+
}
117+
}
118+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH2064
4+
{
5+
public class OneToOneEntity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Linq;
2+
using NHibernate.Cfg.MappingSchema;
3+
using NHibernate.Mapping.ByCode;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.NHSpecificTest.GH2064
7+
{
8+
[TestFixture]
9+
public class OneToOneSelectProjectionFixture : TestCaseMappingByCode
10+
{
11+
protected override HbmMapping GetMappings()
12+
{
13+
var mapper = new ModelMapper();
14+
mapper.Class<OneToOneEntity>(
15+
rc =>
16+
{
17+
rc.Id(e => e.Id, m => m.Generator(Generators.Assigned));
18+
rc.Property(e => e.Name);
19+
});
20+
21+
mapper.Class<ParentEntity>(
22+
rc =>
23+
{
24+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
25+
rc.Property(e => e.Name);
26+
rc.OneToOne(e => e.OneToOne, m => { });
27+
});
28+
29+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
30+
}
31+
32+
protected override void OnSetUp()
33+
{
34+
using (var session = OpenSession())
35+
using (var transaction = session.BeginTransaction())
36+
{
37+
var nullableOwner = new ParentEntity() {Name = "Owner",};
38+
var oneToOne = new OneToOneEntity() {Name = "OneToOne"};
39+
nullableOwner.OneToOne = oneToOne;
40+
session.Save(nullableOwner);
41+
oneToOne.Id = nullableOwner.Id;
42+
session.Save(oneToOne);
43+
session.Flush();
44+
45+
transaction.Commit();
46+
}
47+
}
48+
49+
protected override void OnTearDown()
50+
{
51+
using (var session = OpenSession())
52+
using (var transaction = session.BeginTransaction())
53+
{
54+
// The HQL delete does all the job inside the database without loading the entities, but it does
55+
// not handle delete order for avoiding violating constraints if any. Use
56+
// session.Delete("from System.Object");
57+
// instead if in need of having NHibernate ordering the deletes, but this will cause
58+
// loading the entities in the session.
59+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
60+
61+
transaction.Commit();
62+
}
63+
}
64+
65+
[Test]
66+
public void QueryOneToOne()
67+
{
68+
using (var session = OpenSession())
69+
{
70+
var entity =
71+
session
72+
.Query<ParentEntity>()
73+
.FirstOrDefault();
74+
Assert.That(entity.OneToOne, Is.Not.Null);
75+
}
76+
}
77+
78+
[Test]
79+
public void QueryOneToOneProjection()
80+
{
81+
using (var session = OpenSession())
82+
{
83+
var entity =
84+
session
85+
.Query<ParentEntity>()
86+
.Select(
87+
x => new
88+
{
89+
x.Id,
90+
SubType = new {x.OneToOne, x.Name},
91+
SubType2 = new
92+
{
93+
x.Id,
94+
x.OneToOne,
95+
SubType3 = new {x.Id, x.OneToOne}
96+
},
97+
x.OneToOne
98+
}).FirstOrDefault();
99+
Assert.That(entity.OneToOne, Is.Not.Null);
100+
Assert.That(entity.SubType.OneToOne, Is.Not.Null);
101+
Assert.That(entity.SubType2.OneToOne, Is.Not.Null);
102+
Assert.That(entity.SubType2.SubType3.OneToOne, Is.Not.Null);
103+
}
104+
}
105+
}
106+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH2064
4+
{
5+
public class ParentEntity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
public virtual OneToOneEntity OneToOne { get; set; }
10+
}
11+
}

src/NHibernate/Async/Loader/Hql/QueryLoader.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ protected override async Task<object[]> GetResultRowAsync(object[] row, DbDataRe
7676
resultRow = new object[queryCols];
7777
for (int i = 0; i < queryCols; i++)
7878
{
79-
resultRow[i] = await (ResultTypes[i].NullSafeGetAsync(rs, scalarColumns[i], session, null, cancellationToken)).ConfigureAwait(false);
79+
resultRow[i] = _entityByResultTypeDic.TryGetValue(i, out var rowIndex)
80+
? row[rowIndex]
81+
: await (ResultTypes[i].NullSafeGetAsync(rs, scalarColumns[i], session, null, cancellationToken)).ConfigureAwait(false);
8082
}
8183
}
8284
else

src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,6 @@ public void Prepare()
133133
private IType[] ResolveConstructorArgumentTypes()
134134
{
135135
ISelectExpression[] argumentExpressions = CollectSelectExpressions();
136-
137-
if ( argumentExpressions == null )
138-
{
139-
// return an empty Type array
140-
return Array.Empty<IType>();
141-
}
142-
143136
IType[] types = new IType[argumentExpressions.Length];
144137
for ( int x = 0; x < argumentExpressions.Length; x++ )
145138
{

src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class SelectClause : SelectExpressionList
2323
private IType[] _queryReturnTypes;
2424
private string[][] _columnNames;
2525
private readonly List<FromElement> _fromElementsForLoad = new List<FromElement>();
26+
private readonly Dictionary<int, int> _entityByResultTypeDic = new Dictionary<int, int>();
27+
2628
private ConstructorNode _constructorNode;
2729
private string[] _aliases;
2830
private int[] _columnNamesStartPositions;
@@ -134,19 +136,20 @@ public void InitializeExplicitSelectClause(FromClause fromClause)
134136
if (expr.IsConstructor)
135137
{
136138
_constructorNode = (ConstructorNode)expr;
137-
IList<IType> constructorArgumentTypeList = _constructorNode.ConstructorArgumentTypeList;
138139
//sqlResultTypeList.addAll( constructorArgumentTypeList );
139-
queryReturnTypeList.AddRange(constructorArgumentTypeList);
140140
_scalarSelect = true;
141141

142-
for (int j = 1; j < _constructorNode.ChildCount; j++)
142+
var ctorSelectExpressions = _constructorNode.CollectSelectExpressions();
143+
for (int j = 0; j < ctorSelectExpressions.Length; j++)
143144
{
144-
ISelectExpression se = _constructorNode.GetChild(j) as ISelectExpression;
145+
ISelectExpression se = ctorSelectExpressions[j];
145146

146-
if (se != null && IsReturnableEntity(se))
147+
if (IsReturnableEntity(se))
147148
{
148-
_fromElementsForLoad.Add(se.FromElement);
149+
AddEntityToProjection(queryReturnTypeList.Count, se);
149150
}
151+
152+
queryReturnTypeList.Add(se.DataType);
150153
}
151154
}
152155
else
@@ -163,10 +166,9 @@ public void InitializeExplicitSelectClause(FromClause fromClause)
163166
{
164167
_scalarSelect = true;
165168
}
166-
167-
if (IsReturnableEntity(expr))
169+
else if (IsReturnableEntity(expr))
168170
{
169-
_fromElementsForLoad.Add(expr.FromElement);
171+
AddEntityToProjection(queryReturnTypeList.Count, expr);
170172
}
171173

172174
// Always add the type to the return type list.
@@ -247,6 +249,12 @@ public void InitializeExplicitSelectClause(FromClause fromClause)
247249
FinishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList);
248250
}
249251

252+
private void AddEntityToProjection(int resultIndex, ISelectExpression se)
253+
{
254+
_entityByResultTypeDic[resultIndex] = _fromElementsForLoad.Count;
255+
_fromElementsForLoad.Add(se.FromElement);
256+
}
257+
250258
private static FromElement GetOrigin(FromElement fromElement)
251259
{
252260
var realOrigin = fromElement.RealOrigin;
@@ -271,6 +279,11 @@ public IList<FromElement> FromElementsForLoad
271279
get { return _fromElementsForLoad; }
272280
}
273281

282+
/// <summary>
283+
/// Maps QueryReturnTypes[key] to entities from FromElementsForLoad[value]
284+
/// </summary>
285+
internal IReadOnlyDictionary<int, int> EntityByResultTypeDic => _entityByResultTypeDic;
286+
274287
public bool IsScalarSelect
275288
{
276289
get { return _scalarSelect; }

src/NHibernate/Loader/Hql/QueryLoader.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public partial class QueryLoader : BasicLoader
4747
private IType[] _cacheTypes;
4848
private ISet<ICollectionPersister> _uncacheableCollectionPersisters;
4949
private Dictionary<string, string[]>[] _collectionUserProvidedAliases;
50+
private IReadOnlyDictionary<int, int> _entityByResultTypeDic;
5051

5152
public QueryLoader(QueryTranslatorImpl queryTranslator, ISessionFactoryImplementor factory, SelectClause selectClause)
5253
: base(factory)
@@ -214,6 +215,7 @@ protected override IDictionary<string, string[]> GetCollectionUserProvidedAlias(
214215
private void Initialize(SelectClause selectClause)
215216
{
216217
IList<FromElement> fromElementList = selectClause.FromElementsForLoad;
218+
_entityByResultTypeDic = selectClause.EntityByResultTypeDic;
217219

218220
_hasScalars = selectClause.IsScalarSelect;
219221
_scalarColumnNames = selectClause.ColumnNames;
@@ -401,7 +403,9 @@ protected override object[] GetResultRow(object[] row, DbDataReader rs, ISession
401403
resultRow = new object[queryCols];
402404
for (int i = 0; i < queryCols; i++)
403405
{
404-
resultRow[i] = ResultTypes[i].NullSafeGet(rs, scalarColumns[i], session, null);
406+
resultRow[i] = _entityByResultTypeDic.TryGetValue(i, out var rowIndex)
407+
? row[rowIndex]
408+
: ResultTypes[i].NullSafeGet(rs, scalarColumns[i], session, null);
405409
}
406410
}
407411
else

0 commit comments

Comments
 (0)