Skip to content

Commit e11ec16

Browse files
bahusoidfredericDelaporte
authored andcommitted
Optimize and fix CriteriaQueryTranslator GetColumns and GetTypes (#1974)
* Optimize CriteriaQueryTranslator GetColumns and GetTypes, fixes #780 * Fix GetTypeUsingProjection for multilevel criteria subqueries, fixes #1312
1 parent 74d3786 commit e11ec16

File tree

4 files changed

+262
-50
lines changed

4 files changed

+262
-50
lines changed

src/NHibernate.Test/Async/Criteria/Lambda/SubQueryIntegrationFixture.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,32 @@ public async Task SubQueryAsync()
166166
Assert.That(nameAndChildCount[1].ChildCount, Is.EqualTo(1));
167167
}
168168
}
169+
170+
//NH-3493 - Cannot use alias between more than 1 level of nested queries
171+
[Test]
172+
public async Task ThreeLevelSubqueryAsync()
173+
{
174+
if (!Dialect.SupportsScalarSubSelects)
175+
Assert.Ignore("Dialect does not support scalar sub-select");
176+
177+
Person p = null;
178+
var detachedCriteria2 = DetachedCriteria.For<Person>("vf_inner_2")
179+
.SetProjection(Projections.Id())
180+
.Add(Restrictions.Eq($@"mk.{nameof(p.Age)}", 20))
181+
.Add(Restrictions.EqProperty("vf_inner_2.Id", "vf_inner.Id"));
182+
183+
var detachedCriteria1 = DetachedCriteria.For<Person>("vf_inner")
184+
.SetProjection(Projections.Id())
185+
.Add(Subqueries.Exists(detachedCriteria2))
186+
.Add(Restrictions.EqProperty("vf_inner.Id", "vf.Id"));
187+
188+
using (var s = OpenSession())
189+
{
190+
await (s.CreateCriteria<Person>("vf")
191+
.CreateAlias($"vf.{nameof(p.Father)}", "mk")
192+
.AddOrder(Order.Asc(Projections.SubQuery(detachedCriteria1)))
193+
.ListAsync<Person>());
194+
}
195+
}
169196
}
170197
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using NHibernate.Criterion;
5+
using NUnit.Framework;
6+
7+
namespace NHibernate.Test.Criteria.Lambda
8+
{
9+
[TestFixture, Explicit]
10+
public class CriteriaGenerationBenchmarks : TestCase
11+
{
12+
protected override string MappingsAssembly
13+
{
14+
get { return "NHibernate.Test"; }
15+
}
16+
17+
protected override string[] Mappings
18+
{
19+
get { return new[] { "Criteria.Lambda.Mappings.hbm.xml" }; }
20+
}
21+
22+
protected override void OnSetUp()
23+
{
24+
}
25+
26+
protected override void OnTearDown()
27+
{
28+
}
29+
30+
//NH-1200 - Exception occurs when using criteria exist queries
31+
[Test, Explicit]
32+
public void Subquery()
33+
{
34+
using (var s = OpenSession())
35+
{
36+
Child _subqueryChildAlias = null;
37+
38+
Person person = null;
39+
ICriteria criteria = s.QueryOver<Person>(() => person)
40+
.WithSubquery.WhereExists(
41+
QueryOver.Of<Child>(() => _subqueryChildAlias)
42+
.Where(() => _subqueryChildAlias.Parent.Id == person.Id).Select(c => c.Id)).UnderlyingCriteria;
43+
44+
BenchQuery(s, criteria);
45+
}
46+
}
47+
48+
//NH-1200 - Exception occurs when using criteria exist queries
49+
[Test, Explicit]
50+
public void SubqueryManyParamsFromOuterQuery()
51+
{
52+
using (var s = OpenSession())
53+
{
54+
Child _subqueryChildAlias = null;
55+
56+
Person person = null;
57+
ICriteria criteria = s.QueryOver<Person>(() => person)
58+
.WithSubquery.WhereExists(
59+
QueryOver.Of<Child>(() => _subqueryChildAlias)
60+
.Where(() => _subqueryChildAlias.Age > person.Age && person.Age > 40 && person.Name == "name").Select(c => c.Id)).UnderlyingCriteria;
61+
62+
BenchQuery(s, criteria);
63+
}
64+
}
65+
66+
[Test, Explicit]
67+
public void SimplePropertyCompare()
68+
{
69+
using (var s = OpenSession())
70+
{
71+
ICriteria criteria = s.QueryOver<Person>()
72+
.Where(p => p.Name == "aaa").UnderlyingCriteria;
73+
BenchQuery(s, criteria);
74+
}
75+
}
76+
77+
[Test, Explicit]
78+
public void ManyAliases()
79+
{
80+
using (var s = OpenSession())
81+
{
82+
Child child = null;
83+
Person father = null;
84+
Person person = null;
85+
ICriteria criteria = s.QueryOver<Person>(() => person)
86+
.JoinAlias(p => p.Children, () => child)
87+
.JoinAlias(p => p.Father, () => father)
88+
.Where(p => p.Name == father.Name && p.Father.Id == 10 && child.Nickname == "nickname" && child.Age > person.Age).UnderlyingCriteria;
89+
90+
BenchQuery(s, criteria);
91+
92+
}
93+
}
94+
95+
private static void BenchQuery(ISession s, ICriteria criteria)
96+
{
97+
const int iterations = 15000;
98+
99+
var commands = new List<SqlCommand.ISqlCommand>(iterations);
100+
101+
for (int j = 0; j < 5; j++)
102+
{
103+
using (Timer.Start)
104+
for (int i = 0; i < iterations; i++)
105+
{
106+
var batchItem = new Multi.CriteriaBatchItem<Person>(criteria);
107+
batchItem.Init(s.GetSessionImplementation());
108+
commands.AddRange(batchItem.GetCommands());
109+
}
110+
Console.WriteLine("Elapsed time (ms): " + Timer.ElapsedMilliseconds);
111+
}
112+
}
113+
114+
/// <summary>
115+
/// Stopwatch wrapper
116+
/// </summary>
117+
class Timer : IDisposable
118+
{
119+
static Stopwatch stop = new Stopwatch();
120+
121+
public Timer()
122+
{
123+
stop.Reset();
124+
stop.Start();
125+
}
126+
127+
public static Timer Start { get { return new Timer(); } }
128+
129+
public void Dispose()
130+
{
131+
stop.Stop();
132+
}
133+
134+
static public long ElapsedMilliseconds { get { return stop.ElapsedMilliseconds; } }
135+
}
136+
}
137+
}

src/NHibernate.Test/Criteria/Lambda/SubQueryIntegrationFixture.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,32 @@ public void SubQuery()
155155
Assert.That(nameAndChildCount[1].ChildCount, Is.EqualTo(1));
156156
}
157157
}
158+
159+
//NH-3493 - Cannot use alias between more than 1 level of nested queries
160+
[Test]
161+
public void ThreeLevelSubquery()
162+
{
163+
if (!Dialect.SupportsScalarSubSelects)
164+
Assert.Ignore("Dialect does not support scalar sub-select");
165+
166+
Person p = null;
167+
var detachedCriteria2 = DetachedCriteria.For<Person>("vf_inner_2")
168+
.SetProjection(Projections.Id())
169+
.Add(Restrictions.Eq($@"mk.{nameof(p.Age)}", 20))
170+
.Add(Restrictions.EqProperty("vf_inner_2.Id", "vf_inner.Id"));
171+
172+
var detachedCriteria1 = DetachedCriteria.For<Person>("vf_inner")
173+
.SetProjection(Projections.Id())
174+
.Add(Subqueries.Exists(detachedCriteria2))
175+
.Add(Restrictions.EqProperty("vf_inner.Id", "vf.Id"));
176+
177+
using (var s = OpenSession())
178+
{
179+
s.CreateCriteria<Person>("vf")
180+
.CreateAlias($"vf.{nameof(p.Father)}", "mk")
181+
.AddOrder(Order.Asc(Projections.SubQuery(detachedCriteria1)))
182+
.List<Person>();
183+
}
184+
}
158185
}
159186
}

src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -718,22 +718,14 @@ public string GetColumn(ICriteria criteria, string propertyName)
718718
public string[] GetColumnsUsingProjection(ICriteria subcriteria, string propertyName)
719719
{
720720
// NH Different behavior: we don't use the projection alias for NH-1023
721-
try
722-
{
723-
return GetColumns(subcriteria, propertyName);
724-
}
725-
catch (HibernateException)
726-
{
727-
//not found in inner query , try the outer query
728-
if (outerQueryTranslator != null)
729-
{
730-
return outerQueryTranslator.GetColumnsUsingProjection(subcriteria, propertyName);
731-
}
732-
else
733-
{
734-
throw;
735-
}
736-
}
721+
if (TryGetColumns(subcriteria, propertyName, outerQueryTranslator != null, out var columns))
722+
return columns;
723+
724+
//not found in inner query , try the outer query
725+
if (outerQueryTranslator != null)
726+
return outerQueryTranslator.GetColumnsUsingProjection(subcriteria, propertyName);
727+
728+
throw new QueryException("Could not find property " + propertyName);
737729
}
738730

739731
public string[] GetIdentifierColumns(ICriteria subcriteria)
@@ -755,12 +747,29 @@ public TypedValue GetTypedIdentifierValue(ICriteria subcriteria, object value)
755747

756748
public string[] GetColumns(ICriteria subcriteria, string propertyName)
757749
{
758-
string entName = GetEntityName(subcriteria, propertyName);
759-
if (entName == null)
750+
if (TryGetColumns(subcriteria, propertyName, false, out var columns))
751+
return columns;
752+
753+
throw new QueryException("Could not find property " + propertyName);
754+
}
755+
756+
private bool TryGetColumns(ICriteria subcriteria, string path, bool verifyPropertyName, out string[] columns)
757+
{
758+
if (!TryParseCriteriaPath(subcriteria, path, out var entName, out var propertyName, out var pathCriteria))
760759
{
761-
throw new QueryException("Could not find property " + propertyName);
760+
columns = null;
761+
return false;
762+
}
763+
var propertyMapping = GetPropertyMapping(entName);
764+
765+
if (verifyPropertyName && !propertyMapping.TryToType(propertyName, out var type))
766+
{
767+
columns = null;
768+
return false;
762769
}
763-
return GetPropertyMapping(entName).ToColumns(GetSQLAlias(subcriteria, propertyName), GetPropertyName(propertyName));
770+
771+
columns = propertyMapping.ToColumns(GetSQLAlias(pathCriteria), propertyName);
772+
return true;
764773
}
765774

766775
public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName)
@@ -771,24 +780,18 @@ public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName)
771780

772781
if (projectionTypes == null)
773782
{
774-
try
775-
{
776783
//it does not refer to an alias of a projection,
777784
//look for a property
778-
return GetType(subcriteria, propertyName);
785+
786+
if (TryGetType(subcriteria, propertyName, out var type))
787+
{
788+
return type;
779789
}
780-
catch (HibernateException)
790+
if (outerQueryTranslator != null)
781791
{
782-
//not found in inner query , try the outer query
783-
if (outerQueryTranslator != null)
784-
{
785-
return outerQueryTranslator.GetType(subcriteria, propertyName);
786-
}
787-
else
788-
{
789-
throw;
790-
}
792+
return outerQueryTranslator.GetTypeUsingProjection(subcriteria, propertyName);
791793
}
794+
throw new QueryException("Could not find property " + propertyName);
792795
}
793796
else
794797
{
@@ -803,7 +806,21 @@ public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName)
803806

804807
public IType GetType(ICriteria subcriteria, string propertyName)
805808
{
806-
return GetPropertyMapping(GetEntityName(subcriteria, propertyName)).ToType(GetPropertyName(propertyName));
809+
if(!TryParseCriteriaPath(subcriteria, propertyName, out var entityName, out var entityPropName, out _))
810+
throw new QueryException("Could not find property " + propertyName);
811+
812+
return GetPropertyMapping(entityName).ToType(entityPropName);
813+
}
814+
815+
public bool TryGetType(ICriteria subcriteria, string propertyName, out IType type)
816+
{
817+
if (!TryParseCriteriaPath(subcriteria, propertyName, out var entityName, out var entityPropName, out _))
818+
{
819+
type = null;
820+
return false;
821+
}
822+
823+
return GetPropertyMapping(entityName).TryToType(entityPropName, out type);
807824
}
808825

809826
/// <summary>
@@ -1003,22 +1020,7 @@ public string[] GetColumnAliasesUsingProjection(ICriteria subcriteria, string pr
10031020
{
10041021
//it does not refer to an alias of a projection,
10051022
//look for a property
1006-
try
1007-
{
1008-
return GetColumns(subcriteria, propertyName);
1009-
}
1010-
catch (HibernateException)
1011-
{
1012-
//not found in inner query , try the outer query
1013-
if (outerQueryTranslator != null)
1014-
{
1015-
return outerQueryTranslator.GetColumnAliasesUsingProjection(subcriteria, propertyName);
1016-
}
1017-
else
1018-
{
1019-
throw;
1020-
}
1021-
}
1023+
return GetColumnsUsingProjection(subcriteria, propertyName);
10221024
}
10231025
else
10241026
{
@@ -1052,6 +1054,25 @@ private IQueryable GetQueryablePersister(string entityName)
10521054
{
10531055
return (IQueryable) sessionFactory.GetEntityPersister(entityName);
10541056
}
1057+
1058+
private bool TryParseCriteriaPath(ICriteria subcriteria, string path, out string entityName, out string propertyName, out ICriteria pathCriteria)
1059+
{
1060+
if(StringHelper.IsNotRoot(path, out var root, out var unrootPath))
1061+
{
1062+
ICriteria crit = GetAliasedCriteria(root);
1063+
if (crit != null)
1064+
{
1065+
propertyName = unrootPath;
1066+
entityName = GetEntityName(crit);
1067+
pathCriteria = crit;
1068+
return entityName != null;
1069+
}
1070+
}
1071+
pathCriteria = subcriteria;
1072+
propertyName = path;
1073+
entityName = GetEntityName(subcriteria);
1074+
return entityName != null;
1075+
}
10551076
}
10561077
}
10571078

0 commit comments

Comments
 (0)