diff --git a/src/NHibernate.Test/NHSpecificTest/NH3727/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3727/Entity.cs new file mode 100644 index 00000000000..af7cf52808b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3727/Entity.cs @@ -0,0 +1,7 @@ +namespace NHibernate.Test.NHSpecificTest.NH3727 +{ + class Entity + { + public virtual int Id { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3727/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3727/FixtureByCode.cs new file mode 100644 index 00000000000..77598d80853 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3727/FixtureByCode.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Impl; +using NHibernate.Mapping.ByCode; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3727 +{ + public class ByCodeFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public void QueryOverWithSubqueryProjectionCanBeExecutedMoreThanOnce() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + const int parameter1 = 111; + + var countSubquery = QueryOver.Of() + .Where(x => x.Id == parameter1) //any condition which makes output SQL has parameter + .Select(Projections.RowCount()) + ; + + var originalQueryOver = session.QueryOver() + .SelectList(l => l + .Select(x => x.Id) + .SelectSubQuery(countSubquery) + ) + .TransformUsing(Transformers.ToList); + + originalQueryOver.List(); + + Assert.DoesNotThrow(() => originalQueryOver.List(), "Second try to execute QueryOver thrown exception."); + } + } + + [Test] + public void ClonedQueryOverExecutionMakesOriginalQueryOverNotWorking() + { + // Projections are copied by clone operation. + // SubqueryProjection use SubqueryExpression which holds CriteriaQueryTranslator (class SubqueryExpression { private CriteriaQueryTranslator innerQuery; }) + // So given CriteriaQueryTranslator is used twice. + // Since CriteriaQueryTranslator has CollectedParameters collection, second execution of the Criteria does not fit SqlCommand parameters. + + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + const int parameter1 = 111; + + var countSubquery = QueryOver.Of() + .Where(x => x.Id == parameter1) //any condition which makes output SQL has parameter + .Select(Projections.RowCount()) + ; + + var originalQueryOver = session.QueryOver() + //.Where(x => x.Id == parameter2) + .SelectList(l => l + .Select(x => x.Id) + .SelectSubQuery(countSubquery) + ) + .TransformUsing(Transformers.ToList); + + var clonedQueryOver = originalQueryOver.Clone(); + clonedQueryOver.List(); + + Assert.DoesNotThrow(() => originalQueryOver.List(), "Cloned QueryOver execution caused source QueryOver throw exception when executed."); + } + } + + private static IEnumerable GetProjectionList(IQueryOver clonedQueryOver) + { + var projectionList = (((CriteriaImpl)clonedQueryOver.RootCriteria).Projection as ProjectionList); + for (int i = 0; i < projectionList.Length; i++) + { + yield return projectionList[i]; + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 962022b5487..ac2d03cac8c 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -797,6 +797,8 @@ + + diff --git a/src/NHibernate/Criterion/SubqueryExpression.cs b/src/NHibernate/Criterion/SubqueryExpression.cs index 017e67e8973..bbd7c977afe 100644 --- a/src/NHibernate/Criterion/SubqueryExpression.cs +++ b/src/NHibernate/Criterion/SubqueryExpression.cs @@ -23,7 +23,7 @@ public abstract class SubqueryExpression : AbstractCriterion [NonSerialized] private CriteriaQueryTranslator innerQuery; protected SubqueryExpression(String op, String quantifier, DetachedCriteria dc) - :this(op, quantifier, dc, true) + : this(op, quantifier, dc, true) { } @@ -44,7 +44,7 @@ public IType[] GetTypes() public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary enabledFilters) { - InitializeInnerQueryAndParameters(criteriaQuery); + InitializeInnerQueryAndParameters(criteriaQuery, true); // Force reinitialization. There can be old instance from earlier Criteria execution or cloned Criteria. if (innerQuery.HasProjection == false) { @@ -88,7 +88,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri { buf.Add(quantifier).Add(" "); } - + buf.Add("(").Add(sql).Add(")"); if (quantifier != null && prefixOp == false) @@ -101,9 +101,9 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri public override string ToString() { - if(prefixOp) + if (prefixOp) return string.Format("{0} {1} ({2})", op, quantifier, criteriaImpl); - + return string.Format("{0} ({1}) {2}", op, criteriaImpl, quantifier); } @@ -117,9 +117,9 @@ public override IProjection[] GetProjections() return null; } - public void InitializeInnerQueryAndParameters(ICriteriaQuery criteriaQuery) + public void InitializeInnerQueryAndParameters(ICriteriaQuery criteriaQuery, bool forceReinitialization = false) { - if (innerQuery == null) + if (innerQuery == null || forceReinitialization) { ISessionFactoryImplementor factory = criteriaQuery.Factory;