diff --git a/src/NHibernate.Test/Linq/QueryAsReadOnlyTests.cs b/src/NHibernate.Test/Linq/QueryAsReadOnlyTests.cs new file mode 100644 index 00000000000..1a736b66ee8 --- /dev/null +++ b/src/NHibernate.Test/Linq/QueryAsReadOnlyTests.cs @@ -0,0 +1,35 @@ +using System.Linq; +using NHibernate.AdoNet; +using NHibernate.Cfg; +using NHibernate.Engine; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.Linq +{ + public class QueryAsReadOnlyTests : LinqTestCase + { + [Test] + public void CanSetReadOnlyOnSimpeLinqQueries() + { + //NH-3658 + var result = (from e in db.Customers + where e.CompanyName == "Corp" + select e).AsReadOnly().ToList(); + + Assert.IsTrue(result.All(x => session.IsReadOnly(x))); + } + + [Test] + public void CanSetReadOnlyOnComplexLinqQueries() + { + //NH-3658 + var result = (from e in db.Customers + where e.CompanyName == "Corp" + orderby e.ContactName + select e).Skip(1).Take(10).AsReadOnly().ToList(); + + Assert.IsTrue(result.All(x => session.IsReadOnly(x))); + } + } +} diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 962022b5487..92eea56ce1e 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -519,6 +519,7 @@ + diff --git a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs index 1bab0227632..00496ee8253 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 e9ee33324f0..ef78bd3a686 100755 --- a/src/NHibernate/Linq/LinqExtensionMethods.cs +++ b/src/NHibernate/Linq/LinqExtensionMethods.cs @@ -20,6 +20,15 @@ public static IQueryable Query(this IStatelessSession session) return new NhQueryable(session.GetSessionImplementation()); } + 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 Cacheable(this IQueryable query) { var method = ReflectionHelper.GetMethodDefinition(() => Cacheable(null)).MakeGenericMethod(typeof (T)); diff --git a/src/NHibernate/Linq/NhRelinqQueryParser.cs b/src/NHibernate/Linq/NhRelinqQueryParser.cs index 01c246c5b19..1668a690cba 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,55 @@ 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 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 TimeoutExpressionNode : ResultOperatorExpressionNodeBase { diff --git a/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs b/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs index 1d227529e26..d0e0f255a5c 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/Visitors/QueryModelVisitor.cs b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs index 49f2bd15c3e..9974d72f7c0 100644 --- a/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs +++ b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs @@ -105,6 +105,7 @@ static QueryModelVisitor() ResultOperatorMap.Add(); 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..7e7d8b24d1a --- /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)); + } + } +} \ No newline at end of file diff --git a/src/NHibernate/NHibernate.csproj b/src/NHibernate/NHibernate.csproj index 324f64e8cc3..397488b4859 100644 --- a/src/NHibernate/NHibernate.csproj +++ b/src/NHibernate/NHibernate.csproj @@ -312,6 +312,7 @@ +