Skip to content

Commit 06cdc28

Browse files
committed
Merge pull request #427 from hazzik/NH-3727
NH-3727 - Fix re-using Criteria with SubqueryProjection
2 parents c55bed9 + e4d000e commit 06cdc28

File tree

5 files changed

+110
-36
lines changed

5 files changed

+110
-36
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace NHibernate.Test.NHSpecificTest.NH3727
2+
{
3+
class Entity
4+
{
5+
public virtual int Id { get; set; }
6+
}
7+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using NHibernate.Cfg.MappingSchema;
4+
using NHibernate.Criterion;
5+
using NHibernate.Mapping.ByCode;
6+
using NHibernate.Transform;
7+
using NUnit.Framework;
8+
9+
namespace NHibernate.Test.NHSpecificTest.NH3727
10+
{
11+
public class ByCodeFixture : TestCaseMappingByCode
12+
{
13+
protected override HbmMapping GetMappings()
14+
{
15+
var mapper = new ModelMapper();
16+
mapper.Class<Entity>(rc =>
17+
{
18+
rc.Id(x => x.Id);
19+
});
20+
21+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
22+
}
23+
24+
[Test]
25+
public void QueryOverWithSubqueryProjectionCanBeExecutedMoreThanOnce()
26+
{
27+
using (ISession session = OpenSession())
28+
using (session.BeginTransaction())
29+
{
30+
const int parameter1 = 111;
31+
32+
var countSubquery = QueryOver.Of<Entity>()
33+
.Where(x => x.Id == parameter1) //any condition which makes output SQL has parameter
34+
.Select(Projections.RowCount())
35+
;
36+
37+
var originalQueryOver = session.QueryOver<Entity>()
38+
.SelectList(l => l
39+
.Select(x => x.Id)
40+
.SelectSubQuery(countSubquery)
41+
)
42+
.TransformUsing(Transformers.ToList);
43+
44+
var objects = originalQueryOver.List<object>();
45+
46+
Assert.DoesNotThrow(() => originalQueryOver.List<object>(), "Second try to execute QueryOver thrown exception.");
47+
}
48+
}
49+
50+
[Test]
51+
public void ClonedQueryOverExecutionMakesOriginalQueryOverNotWorking()
52+
{
53+
// Projections are copied by clone operation.
54+
// SubqueryProjection use SubqueryExpression which holds CriteriaQueryTranslator (class SubqueryExpression { private CriteriaQueryTranslator innerQuery; })
55+
// So given CriteriaQueryTranslator is used twice.
56+
// Since CriteriaQueryTranslator has CollectedParameters collection, second execution of the Criteria does not fit SqlCommand parameters.
57+
58+
using (ISession session = OpenSession())
59+
using (session.BeginTransaction())
60+
{
61+
const int parameter1 = 111;
62+
63+
var countSubquery = QueryOver.Of<Entity>()
64+
.Where(x => x.Id == parameter1) //any condition which makes output SQL has parameter
65+
.Select(Projections.RowCount())
66+
;
67+
68+
var originalQueryOver = session.QueryOver<Entity>()
69+
//.Where(x => x.Id == parameter2)
70+
.SelectList(l => l
71+
.Select(x => x.Id)
72+
.SelectSubQuery(countSubquery)
73+
)
74+
.TransformUsing(Transformers.ToList);
75+
76+
var clonedQueryOver = originalQueryOver.Clone();
77+
clonedQueryOver.List<object>();
78+
79+
Assert.DoesNotThrow(() => originalQueryOver.List<object>(), "Cloned QueryOver execution caused source QueryOver throw exception when executed.");
80+
}
81+
}
82+
}
83+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,8 @@
869869
<Compile Include="NHSpecificTest\NH3634\CachedPerson.cs" />
870870
<Compile Include="NHSpecificTest\NH3634\CachedPersonMapper.cs" />
871871
<Compile Include="NHSpecificTest\NH3634\PersonMapper.cs" />
872+
<Compile Include="NHSpecificTest\NH3727\Entity.cs" />
873+
<Compile Include="NHSpecificTest\NH3727\FixtureByCode.cs" />
872874
<Compile Include="NHSpecificTest\NH646\Domain.cs" />
873875
<Compile Include="NHSpecificTest\NH646\Fixture.cs" />
874876
<Compile Include="NHSpecificTest\NH3234\Fixture.cs" />

src/NHibernate/Criterion/SubqueryExpression.cs

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ public abstract class SubqueryExpression : AbstractCriterion
2020
private QueryParameters parameters;
2121
private IType[] types;
2222

23-
[NonSerialized] private CriteriaQueryTranslator innerQuery;
24-
2523
protected SubqueryExpression(String op, String quantifier, DetachedCriteria dc)
26-
:this(op, quantifier, dc, true)
24+
: this(op, quantifier, dc, true)
2725
{
2826
}
2927

@@ -44,16 +42,23 @@ public IType[] GetTypes()
4442

4543
public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
4644
{
47-
InitializeInnerQueryAndParameters(criteriaQuery);
45+
ISessionFactoryImplementor factory = criteriaQuery.Factory;
46+
47+
var innerQuery = new CriteriaQueryTranslator(
48+
factory,
49+
criteriaImpl, //implicit polymorphism not supported (would need a union)
50+
criteriaImpl.EntityOrClassName,
51+
criteriaQuery.GenerateSQLAlias(),
52+
criteriaQuery);
53+
54+
types = innerQuery.HasProjection ? innerQuery.ProjectedTypes : null;
4855

4956
if (innerQuery.HasProjection == false)
5057
{
5158
throw new QueryException("Cannot use subqueries on a criteria without a projection.");
5259
}
5360

54-
ISessionFactoryImplementor factory = criteriaQuery.Factory;
55-
56-
IOuterJoinLoadable persister = (IOuterJoinLoadable)factory.GetEntityPersister(criteriaImpl.EntityOrClassName);
61+
IOuterJoinLoadable persister = (IOuterJoinLoadable) factory.GetEntityPersister(criteriaImpl.EntityOrClassName);
5762

5863
//patch to generate joins on subqueries
5964
//stolen from CriteriaLoader
@@ -73,11 +78,6 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri
7378
sql = factory.Dialect.GetLimitString(sql, offset, limit, offsetParameter, limitParameter);
7479
}
7580

76-
// during CriteriaImpl.Clone we are doing a shallow copy of each criterion.
77-
// this is not a problem for common criterion but not for SubqueryExpression because here we are holding the state of inner CriteriaTraslator (ICriteriaQuery).
78-
// After execution (ToSqlString) we have to clean the internal state because the next execution may be performed in a different tree reusing the same istance of SubqueryExpression.
79-
innerQuery = null;
80-
8181
SqlStringBuilder buf = new SqlStringBuilder().Add(ToLeftSqlString(criteria, criteriaQuery));
8282
if (op != null)
8383
{
@@ -88,7 +88,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri
8888
{
8989
buf.Add(quantifier).Add(" ");
9090
}
91-
91+
9292
buf.Add("(").Add(sql).Add(")");
9393

9494
if (quantifier != null && prefixOp == false)
@@ -101,9 +101,9 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri
101101

102102
public override string ToString()
103103
{
104-
if(prefixOp)
104+
if (prefixOp)
105105
return string.Format("{0} {1} ({2})", op, quantifier, criteriaImpl);
106-
106+
107107
return string.Format("{0} ({1}) {2}", op, criteriaImpl, quantifier);
108108
}
109109

@@ -117,23 +117,6 @@ public override IProjection[] GetProjections()
117117
return null;
118118
}
119119

120-
public void InitializeInnerQueryAndParameters(ICriteriaQuery criteriaQuery)
121-
{
122-
if (innerQuery == null)
123-
{
124-
ISessionFactoryImplementor factory = criteriaQuery.Factory;
125-
126-
innerQuery = new CriteriaQueryTranslator(
127-
factory,
128-
criteriaImpl, //implicit polymorphism not supported (would need a union)
129-
criteriaImpl.EntityOrClassName,
130-
criteriaQuery.GenerateSQLAlias(),
131-
criteriaQuery);
132-
133-
types = innerQuery.HasProjection ? innerQuery.ProjectedTypes : null;
134-
}
135-
}
136-
137120
public ICriteria Criteria
138121
{
139122
// NH-1146

src/NHibernate/Criterion/SubqueryProjection.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace NHibernate.Criterion
1313
[Serializable]
1414
public class SubqueryProjection : SimpleProjection
1515
{
16-
private SelectSubqueryExpression _subQuery;
16+
private readonly SelectSubqueryExpression _subQuery;
1717

1818
protected internal SubqueryProjection(SelectSubqueryExpression subquery)
1919
{
@@ -37,14 +37,13 @@ public override bool IsAggregate
3737

3838
public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
3939
{
40-
_subQuery.InitializeInnerQueryAndParameters(criteriaQuery);
4140
return _subQuery.GetTypes();
4241
}
4342

4443
public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
4544
{
46-
SqlString sqlStringSubquery = _subQuery.ToSqlString(criteria, criteriaQuery, enabledFilters);
47-
return sqlStringSubquery.Append(new SqlString(new object[] { " as y", loc.ToString(), "_" } ));
45+
return _subQuery.ToSqlString(criteria, criteriaQuery, enabledFilters)
46+
.Append(new SqlString(" as y", loc.ToString(), "_"));
4847
}
4948

5049
public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)

0 commit comments

Comments
 (0)