From fdb0ddac10a6fd94a12b59f0e4f6a0d634333d72 Mon Sep 17 00:00:00 2001 From: Ricardo Peres Date: Fri, 14 Mar 2014 10:12:43 +0000 Subject: [PATCH] NH-3470 --- .../Linq/QueryReadOnlyTests.cs | 68 +++++++++++++++++++ src/NHibernate.Test/NHibernate.Test.csproj | 1 + .../GroupBy/AggregatingGroupByRewriter.cs | 1 + src/NHibernate/Linq/LinqExtensionMethods.cs | 8 +++ src/NHibernate/Linq/NhRelinqQueryParser.cs | 55 +++++++++++++++ .../QueryReferenceExpressionFlattener.cs | 1 + .../Linq/ReWriters/ResultOperatorRewriter.cs | 1 + .../Linq/Visitors/QueryModelVisitor.cs | 1 + .../ProcessAsReadOnly.cs | 15 ++++ src/NHibernate/NHibernate.csproj | 1 + 10 files changed, 152 insertions(+) create mode 100644 src/NHibernate.Test/Linq/QueryReadOnlyTests.cs create mode 100644 src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessAsReadOnly.cs diff --git a/src/NHibernate.Test/Linq/QueryReadOnlyTests.cs b/src/NHibernate.Test/Linq/QueryReadOnlyTests.cs new file mode 100644 index 00000000000..c52a442c94b --- /dev/null +++ b/src/NHibernate.Test/Linq/QueryReadOnlyTests.cs @@ -0,0 +1,68 @@ +using System.Linq; +using NHibernate.AdoNet; +using NHibernate.Cfg; +using NHibernate.Engine; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.Linq +{ + public class QueryReadOnlyTests : LinqTestCase + { + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + } + + [Test] + public void CanSetReadOnlyOnLinqQueries() + { + var result = (from e in db.Customers + where e.CompanyName == "Bon app'" + select e).AsReadOnly().ToList(); + + Assert.That(this.session.IsReadOnly(result.First()), Is.True); + } + + + [Test] + public void CanSetReadOnlyOnLinqPagingQuery() + { + var result = (from e in db.Customers + where e.CompanyName == "Bon app'" + select e).Skip(5).Take(5).AsReadOnly().ToList(); + + Assert.That(this.session.IsReadOnly(result.First()), Is.True); + } + + + [Test] + public void CanSetReadOnlyBeforeSkipOnLinqOrderedPageQuery() + { + var result = (from e in db.Customers + orderby e.CompanyName + select e) + .AsReadOnly().Skip(5).Take(5).ToList(); + + Assert.That(this.session.IsReadOnly(result.First()), Is.True); + } + + + [Test] + public void CanSetReadOnlyOnLinqGroupPageQuery() + { + var subQuery = db.Customers.Where(e2 => e2.CompanyName.Contains("a")).Select(e2 => e2.CustomerId) + .AsReadOnly(); // This AsReadOnly() should not cause trouble, and be ignored. + + var result = (from e in db.Customers + where subQuery.Contains(e.CustomerId) + group e by e.CompanyName + into g + select new { g.Key, Count = g.Count() }) + .Skip(5).Take(5) + .AsReadOnly().ToList(); + + Assert.That(this.session.IsReadOnly(result.First()), Is.True); + } + } +} diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 7c49aefb6ca..15670a67d09 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -516,6 +516,7 @@ + diff --git a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs index e7b3acd7654..3186fcd9693 100644 --- a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs +++ b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs @@ -38,6 +38,7 @@ public static class AggregatingGroupByRewriter typeof (AnyResultOperator), typeof (AllResultOperator), typeof (TimeoutResultOperator), + typeof (AsReadOnlyResultOperator), }; public static void ReWrite(QueryModel queryModel) diff --git a/src/NHibernate/Linq/LinqExtensionMethods.cs b/src/NHibernate/Linq/LinqExtensionMethods.cs index c8b9acb2c0b..babfd1817de 100755 --- a/src/NHibernate/Linq/LinqExtensionMethods.cs +++ b/src/NHibernate/Linq/LinqExtensionMethods.cs @@ -47,6 +47,14 @@ public static IQueryable CacheRegion(this IQueryable query, string regi return new NhQueryable(query.Provider, callExpression); } + public static IQueryable AsReadOnly(this IQueryable query) + { + var method = ReflectionHelper.GetMethodDefinition(() => AsReadOnly(null)).MakeGenericMethod(typeof(T)); + + var callExpression = Expression.Call(method, query.Expression); + + return new NhQueryable(query.Provider, callExpression); + } public static IQueryable Timeout(this IQueryable query, int timeout) { diff --git a/src/NHibernate/Linq/NhRelinqQueryParser.cs b/src/NHibernate/Linq/NhRelinqQueryParser.cs index 4ef1f85ac29..c3864c01f5e 100644 --- a/src/NHibernate/Linq/NhRelinqQueryParser.cs +++ b/src/NHibernate/Linq/NhRelinqQueryParser.cs @@ -78,6 +78,13 @@ public NHibernateNodeTypeProvider() }, typeof (TimeoutExpressionNode) ); + methodInfoRegistry.Register( + new[] + { + ReflectionHelper.GetMethodDefinition(() => LinqExtensionMethods.AsReadOnly(null)), + }, typeof(AsReadOnlyExpressionNode) + ); + var nodeTypeProvider = ExpressionTreeParser.CreateDefaultNodeTypeProvider(); nodeTypeProvider.InnerProviders.Add(methodInfoRegistry); defaultNodeTypeProvider = nodeTypeProvider; @@ -168,6 +175,25 @@ public override void TransformExpressions(Func transform } } + internal class AsReadOnlyExpressionNode : ResultOperatorExpressionNodeBase + { + private readonly MethodCallExpressionParseInfo _parseInfo; + + public AsReadOnlyExpressionNode(MethodCallExpressionParseInfo parseInfo) : base(parseInfo, null, null) + { + _parseInfo = parseInfo; + } + + public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, ClauseGenerationContext clauseGenerationContext) + { + return Source.Resolve(inputParameter, expressionToBeResolved, clauseGenerationContext); + } + + protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) + { + return new AsReadOnlyResultOperator(_parseInfo); + } + } internal class TimeoutExpressionNode : ResultOperatorExpressionNodeBase { @@ -192,6 +218,35 @@ protected override ResultOperatorBase CreateResultOperator(ClauseGenerationConte } } + internal class AsReadOnlyResultOperator : ResultOperatorBase + { + public MethodCallExpressionParseInfo ParseInfo { get; private set; } + + public AsReadOnlyResultOperator(MethodCallExpressionParseInfo parseInfo) + { + ParseInfo = parseInfo; + } + + public override IStreamedData ExecuteInMemory(IStreamedData input) + { + throw new NotImplementedException(); + } + + public override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo inputInfo) + { + return inputInfo; + } + + public override ResultOperatorBase Clone(CloneContext cloneContext) + { + throw new NotImplementedException(); + } + + public override void TransformExpressions(Func transformation) + { + } + } + internal class TimeoutResultOperator : ResultOperatorBase { public MethodCallExpressionParseInfo ParseInfo { get; private set; } diff --git a/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs b/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs index 00da7152879..0f6b6e23826 100644 --- a/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs +++ b/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs @@ -16,6 +16,7 @@ public class QueryReferenceExpressionFlattener : ExpressionTreeVisitor { typeof(CacheableResultOperator), typeof (TimeoutResultOperator), + typeof (AsReadOnlyResultOperator), }; private QueryReferenceExpressionFlattener(QueryModel model) diff --git a/src/NHibernate/Linq/ReWriters/ResultOperatorRewriter.cs b/src/NHibernate/Linq/ReWriters/ResultOperatorRewriter.cs index 553f9ff27e1..6d25f33d889 100644 --- a/src/NHibernate/Linq/ReWriters/ResultOperatorRewriter.cs +++ b/src/NHibernate/Linq/ReWriters/ResultOperatorRewriter.cs @@ -67,6 +67,7 @@ private class ResultOperatorExpressionRewriter : ExpressionTreeVisitor typeof(OfTypeResultOperator), typeof(CacheableResultOperator), typeof(TimeoutResultOperator), + typeof(AsReadOnlyResultOperator), typeof(CastResultOperator), // see ProcessCast class }; diff --git a/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs index 0257395aa63..b47a1d4147d 100644 --- a/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs +++ b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs @@ -106,6 +106,7 @@ static QueryModelVisitor() ResultOperatorMap.Add(); ResultOperatorMap.Add(); ResultOperatorMap.Add(); + ResultOperatorMap.Add(); ResultOperatorMap.Add(); ResultOperatorMap.Add(); } diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessAsReadOnly.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessAsReadOnly.cs new file mode 100644 index 00000000000..1e86facd10f --- /dev/null +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessAsReadOnly.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NHibernate.Linq.Visitors.ResultOperatorProcessors +{ + internal class ProcessAsReadOnly : IResultOperatorProcessor + { + public void Process(AsReadOnlyResultOperator resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree) + { + tree.AddAdditionalCriteria((q, p) => q.SetReadOnly(true)); + } + } +} diff --git a/src/NHibernate/NHibernate.csproj b/src/NHibernate/NHibernate.csproj index e9601f1cf47..a8b24c7a67a 100644 --- a/src/NHibernate/NHibernate.csproj +++ b/src/NHibernate/NHibernate.csproj @@ -311,6 +311,7 @@ +