Skip to content

Commit 73f0c6c

Browse files
committed
Add support for criteria aliases in SqlProjection and SQLCriterion
Fixes #896
1 parent 4bd76b2 commit 73f0c6c

File tree

6 files changed

+226
-8
lines changed

6 files changed

+226
-8
lines changed

src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Collections.Generic;
1414
using NHibernate.Dialect;
1515
using NHibernate.Criterion;
16+
using NHibernate.Linq;
1617
using NHibernate.SqlCommand;
1718
using NHibernate.Transform;
1819
using NHibernate.Type;
@@ -922,6 +923,91 @@ public async Task ProjectionsTestAsync()
922923
s.Close();
923924
}
924925

926+
[Test]
927+
public async Task TestSQLProjectionWithAliasesAsync()
928+
{
929+
using(ISession s = OpenSession())
930+
using(ITransaction t = s.BeginTransaction())
931+
{
932+
Course course = new Course();
933+
course.CourseCode = "HIB";
934+
course.Description = "Hibernate Training";
935+
await (s.SaveAsync(course));
936+
937+
Student gavin = new Student();
938+
gavin.Name = "Gavin King";
939+
gavin.StudentNumber = 667;
940+
await (s.SaveAsync(gavin));
941+
942+
Student xam = new Student();
943+
xam.Name = "Max Rydahl Andersen";
944+
xam.StudentNumber = 101;
945+
await (s.SaveAsync(xam));
946+
947+
Enrolment enrolment = new Enrolment();
948+
enrolment.Course = course;
949+
enrolment.CourseCode = course.CourseCode;
950+
enrolment.Semester = 1;
951+
enrolment.Year = 1999;
952+
enrolment.Student = xam;
953+
enrolment.StudentNumber = xam.StudentNumber;
954+
xam.Enrolments.Add(enrolment);
955+
await (s.SaveAsync(enrolment));
956+
957+
enrolment = new Enrolment();
958+
enrolment.Course = course;
959+
enrolment.CourseCode = course.CourseCode;
960+
enrolment.Semester = 3;
961+
enrolment.Year = 1998;
962+
enrolment.Student = gavin;
963+
enrolment.StudentNumber = gavin.StudentNumber;
964+
gavin.Enrolments.Add(enrolment);
965+
await (s.SaveAsync(enrolment));
966+
await (t.CommitAsync());
967+
}
968+
969+
using (var s = OpenSession())
970+
{
971+
Student studentSubquery = null;
972+
var subquery = QueryOver.Of(() => studentSubquery)
973+
.And(
974+
Expression.Sql("{e}.studentId = 667 and {studentSubquery}.studentId = 667")).Select(Projections.Id());
975+
976+
var uniqueResult = await (s.CreateCriteria(typeof(Student))
977+
.Add(Subqueries.Exists(subquery.DetachedCriteria))
978+
.AddOrder(Order.Asc("Name"))
979+
.CreateCriteria("Enrolments", "e")
980+
.AddOrder(Order.Desc("Year"))
981+
.AddOrder(Order.Desc("Semester"))
982+
.CreateCriteria("Course", "c")
983+
.AddOrder(Order.Asc("Description"))
984+
.SetProjection(
985+
Projections.SqlProjection(
986+
"{alias}.studentId as studentNumber, {e}.Semester as semester,"
987+
+ " {c}.CourseCode as courseCode, {c}.Description as descr",
988+
new string[] {"studentNumber", "semester", "courseCode", "descr"},
989+
new[]
990+
{
991+
TypeFactory.HeuristicType(typeof(long)),
992+
TypeFactory.HeuristicType(typeof(short)),
993+
TypeFactory.HeuristicType(typeof(string)),
994+
TypeFactory.HeuristicType(typeof(string)),
995+
}))
996+
.UniqueResultAsync());
997+
998+
Assert.That(uniqueResult, Is.Not.Null);
999+
}
1000+
1001+
using (var s = OpenSession())
1002+
using (s.BeginTransaction())
1003+
{
1004+
await (s.Query<Enrolment>().DeleteAsync());
1005+
await (s.Query<Student>().DeleteAsync());
1006+
await (s.Query<Course>().DeleteAsync());
1007+
await (s.GetCurrentTransaction().CommitAsync());
1008+
}
1009+
}
1010+
9251011
[Test]
9261012
public async Task CloningProjectionsTestAsync()
9271013
{

src/NHibernate.Test/Criteria/CriteriaQueryTest.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using NHibernate.Dialect;
55
using NHibernate.Criterion;
6+
using NHibernate.Linq;
67
using NHibernate.SqlCommand;
78
using NHibernate.Transform;
89
using NHibernate.Type;
@@ -1019,6 +1020,91 @@ public void ProjectionsTest()
10191020
s.Close();
10201021
}
10211022

1023+
[Test]
1024+
public void TestSQLProjectionWithAliases()
1025+
{
1026+
using(ISession s = OpenSession())
1027+
using(ITransaction t = s.BeginTransaction())
1028+
{
1029+
Course course = new Course();
1030+
course.CourseCode = "HIB";
1031+
course.Description = "Hibernate Training";
1032+
s.Save(course);
1033+
1034+
Student gavin = new Student();
1035+
gavin.Name = "Gavin King";
1036+
gavin.StudentNumber = 667;
1037+
s.Save(gavin);
1038+
1039+
Student xam = new Student();
1040+
xam.Name = "Max Rydahl Andersen";
1041+
xam.StudentNumber = 101;
1042+
s.Save(xam);
1043+
1044+
Enrolment enrolment = new Enrolment();
1045+
enrolment.Course = course;
1046+
enrolment.CourseCode = course.CourseCode;
1047+
enrolment.Semester = 1;
1048+
enrolment.Year = 1999;
1049+
enrolment.Student = xam;
1050+
enrolment.StudentNumber = xam.StudentNumber;
1051+
xam.Enrolments.Add(enrolment);
1052+
s.Save(enrolment);
1053+
1054+
enrolment = new Enrolment();
1055+
enrolment.Course = course;
1056+
enrolment.CourseCode = course.CourseCode;
1057+
enrolment.Semester = 3;
1058+
enrolment.Year = 1998;
1059+
enrolment.Student = gavin;
1060+
enrolment.StudentNumber = gavin.StudentNumber;
1061+
gavin.Enrolments.Add(enrolment);
1062+
s.Save(enrolment);
1063+
t.Commit();
1064+
}
1065+
1066+
using (var s = OpenSession())
1067+
{
1068+
Student studentSubquery = null;
1069+
var subquery = QueryOver.Of(() => studentSubquery)
1070+
.And(
1071+
Expression.Sql("{e}.studentId = 667 and {studentSubquery}.studentId = 667")).Select(Projections.Id());
1072+
1073+
var uniqueResult = s.CreateCriteria(typeof(Student))
1074+
.Add(Subqueries.Exists(subquery.DetachedCriteria))
1075+
.AddOrder(Order.Asc("Name"))
1076+
.CreateCriteria("Enrolments", "e")
1077+
.AddOrder(Order.Desc("Year"))
1078+
.AddOrder(Order.Desc("Semester"))
1079+
.CreateCriteria("Course", "c")
1080+
.AddOrder(Order.Asc("Description"))
1081+
.SetProjection(
1082+
Projections.SqlProjection(
1083+
"{alias}.studentId as studentNumber, {e}.Semester as semester,"
1084+
+ " {c}.CourseCode as courseCode, {c}.Description as descr",
1085+
new string[] {"studentNumber", "semester", "courseCode", "descr"},
1086+
new[]
1087+
{
1088+
TypeFactory.HeuristicType(typeof(long)),
1089+
TypeFactory.HeuristicType(typeof(short)),
1090+
TypeFactory.HeuristicType(typeof(string)),
1091+
TypeFactory.HeuristicType(typeof(string)),
1092+
}))
1093+
.UniqueResult();
1094+
1095+
Assert.That(uniqueResult, Is.Not.Null);
1096+
}
1097+
1098+
using (var s = OpenSession())
1099+
using (s.BeginTransaction())
1100+
{
1101+
s.Query<Enrolment>().Delete();
1102+
s.Query<Student>().Delete();
1103+
s.Query<Course>().Delete();
1104+
s.GetCurrentTransaction().Commit();
1105+
}
1106+
}
1107+
10221108
[Test]
10231109
public void CloningProjectionsTest()
10241110
{

src/NHibernate/Criterion/ICriteriaQuery.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
using System.Collections.Generic;
22
using NHibernate.Engine;
3+
using NHibernate.Loader.Criteria;
34
using NHibernate.Param;
45
using NHibernate.SqlCommand;
56
using NHibernate.Type;
7+
using NHibernate.Util;
68

79
namespace NHibernate.Criterion
810
{
11+
//TODO 6.0: Add to ICriteriaQuery interface
12+
internal static class CriteriaQueryExtensions
13+
{
14+
/// <summary>
15+
/// Get the criteria alias to SQL alias map
16+
/// </summary>
17+
public static IDictionary<string, string> GetCriteriaSQLAliasMap(this ICriteriaQuery criteriaQuery)
18+
{
19+
if (criteriaQuery is CriteriaQueryTranslator translator)
20+
{
21+
return translator.GetCriteriaSQLAliasMap();
22+
}
23+
24+
return CollectionHelper.EmptyDictionary<string, string>();
25+
}
26+
}
27+
928
/// <summary>
1029
/// An instance of <see cref="ICriteriaQuery"/> is passed to criterion,
1130
/// order and projection instances when actually compiling and
@@ -80,4 +99,4 @@ public interface ICriteriaQuery
8099
Parameter CreateSkipParameter(int value);
81100
Parameter CreateTakeParameter(int value);
82101
}
83-
}
102+
}

src/NHibernate/Criterion/SQLCriterion.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace NHibernate.Criterion
99
/// <summary>
1010
/// An <see cref="ICriterion"/> that creates a SQLExpression.
1111
/// The string {alias} will be replaced by the alias of the root entity.
12+
/// Criteria aliases can also be used: "{a}.Value + {bc}.Value".
1213
/// </summary>
1314
/// <remarks>
1415
/// This allows for database specific Expressions at the cost of needing to
@@ -42,7 +43,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri
4243
parameters[paramPos++].BackTrack = parameter.BackTrack;
4344
}
4445
}
45-
return _sql.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria));
46+
return SQLProjection.GetSqlString(criteria, criteriaQuery, _sql);
4647
}
4748

4849
public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)

src/NHibernate/Criterion/SQLProjection.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
using System;
22
using NHibernate.SqlCommand;
33
using NHibernate.Type;
4-
using NHibernate.Util;
54

65
namespace NHibernate.Criterion
76
{
87
using Engine;
98

109
/// <summary>
1110
/// A SQL fragment. The string {alias} will be replaced by the alias of the root entity.
11+
/// Criteria aliases can also be used: "{a}.Value + {bc}.Value".
1212
/// </summary>
1313
[Serializable]
1414
public sealed class SQLProjection : IProjection
@@ -37,15 +37,27 @@ internal SQLProjection(string sql, string groupBy, string[] columnAliases, IType
3737

3838
public SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery)
3939
{
40-
//SqlString result = new SqlString(criteriaQuery.GetSQLAlias(criteria));
41-
//result.Replace(sql, "{alias}");
42-
//return result;
43-
return new SqlString(sql?.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)));
40+
return GetSqlString(criteria, criteriaQuery, sql);
4441
}
4542

4643
public SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)
4744
{
48-
return new SqlString(groupBy?.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)));
45+
return GetSqlString(criteria, criteriaQuery, groupBy);
46+
}
47+
48+
private SqlString GetSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, string sqlTemplate)
49+
{
50+
return GetSqlString(criteria, criteriaQuery, new SqlString(sqlTemplate));
51+
}
52+
53+
internal static SqlString GetSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, SqlString sqlTemplate)
54+
{
55+
foreach (var kvp in criteriaQuery.GetCriteriaSQLAliasMap())
56+
{
57+
sqlTemplate = sqlTemplate.Replace("{" + kvp.Key + "}", kvp.Value);
58+
}
59+
60+
return sqlTemplate.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria));
4961
}
5062

5163
public override string ToString()

src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,20 @@ public string GetEntityName(ICriteria subcriteria, string propertyName)
863863
return GetEntityName(subcriteria);
864864
}
865865

866+
internal IDictionary<string, string> GetCriteriaSQLAliasMap()
867+
{
868+
var result = criteriaSQLAliasMap.Where(p => !string.IsNullOrEmpty(p.Key.Alias)).ToDictionary(p => p.Key.Alias, p => p.Value);
869+
if (outerQueryTranslator != null)
870+
{
871+
foreach (var p in outerQueryTranslator.GetCriteriaSQLAliasMap())
872+
{
873+
result.Add(p.Key, p.Value);
874+
}
875+
}
876+
877+
return result;
878+
}
879+
866880
public string GetSQLAlias(ICriteria criteria, string propertyName)
867881
{
868882
if (StringHelper.IsNotRoot(propertyName, out var root))

0 commit comments

Comments
 (0)