diff --git a/src/NHibernate.Test/Linq/QueryReadOnlyTests.cs b/src/NHibernate.Test/Linq/QueryReadOnlyTests.cs new file mode 100644 index 00000000000..7d315ad8653 --- /dev/null +++ b/src/NHibernate.Test/Linq/QueryReadOnlyTests.cs @@ -0,0 +1,59 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.Linq +{ + public class QueryReadOnlyTests : LinqTestCase + { + [Test] + public void CanSetReadOnlyOnLinqQueries() + { + var result = (from e in db.Customers + where e.CompanyName == "Bon app'" + select e).AsReadOnly().ToList(); + + Assert.That(result.All(x => this.session.IsReadOnly(x)), Is.True); + } + + + [Test] + public void CanSetReadOnlyOnLinqPagingQuery() + { + var result = (from e in db.Customers + select e).Skip(1).Take(1).AsReadOnly().ToList(); + + Assert.That(result.All(x => this.session.IsReadOnly(x)), 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(result.All(x => this.session.IsReadOnly(x)), 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(result.All(x => this.session.IsReadOnly(x)), Is.True); + } + } +} diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 9c8bd6d397d..0490b639b23 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -547,6 +547,7 @@ + diff --git a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs index 8150d0ae5df..603a9b211ad 100644 --- a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs +++ b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs @@ -40,6 +40,7 @@ public static class AggregatingGroupByRewriter typeof (AnyResultOperator), typeof (AllResultOperator), typeof (TimeoutResultOperator), + typeof (AsReadOnlyResultOperator), typeof (CacheableResultOperator) }; diff --git a/src/NHibernate/Linq/LinqExtensionMethods.cs b/src/NHibernate/Linq/LinqExtensionMethods.cs index dafee708975..47f9376089f 100755 --- a/src/NHibernate/Linq/LinqExtensionMethods.cs +++ b/src/NHibernate/Linq/LinqExtensionMethods.cs @@ -58,6 +58,15 @@ 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) { var method = ReflectionHelper.GetMethodDefinition(() => Timeout(null, 0)).MakeGenericMethod(typeof(T)); @@ -73,9 +82,9 @@ public static IEnumerable ToFuture(this IQueryable query) if (nhQueryable == null) throw new NotSupportedException("Query needs to be of type QueryableBase"); - var provider = (INhQueryProvider) nhQueryable.Provider; + var provider = (INhQueryProvider)nhQueryable.Provider; var future = provider.ExecuteFuture(nhQueryable.Expression); - return (IEnumerable) future; + return (IEnumerable)future; } public static IFutureValue ToFutureValue(this IQueryable query) @@ -84,14 +93,14 @@ public static IFutureValue ToFutureValue(this IQueryable query) if (nhQueryable == null) throw new NotSupportedException("Query needs to be of type QueryableBase"); - var provider = (INhQueryProvider) nhQueryable.Provider; + var provider = (INhQueryProvider)nhQueryable.Provider; var future = provider.ExecuteFuture(nhQueryable.Expression); if (future is IEnumerable) { - return new FutureValue(() => ((IEnumerable) future)); + return new FutureValue(() => ((IEnumerable)future)); } - return (IFutureValue) future; + return (IFutureValue)future; } public static T MappedAs(this T parameter, IType type) @@ -105,13 +114,13 @@ public static IFutureValue ToFutureValue(this IQueryable if (nhQueryable == null) throw new NotSupportedException("Query needs to be of type QueryableBase"); - var provider = (INhQueryProvider) query.Provider; + var provider = (INhQueryProvider)query.Provider; var expression = ReplacingExpressionTreeVisitor.Replace(selector.Parameters.Single(), query.Expression, selector.Body); - return (IFutureValue) provider.ExecuteFuture(expression); + return (IFutureValue)provider.ExecuteFuture(expression); } } } diff --git a/src/NHibernate/Linq/NhRelinqQueryParser.cs b/src/NHibernate/Linq/NhRelinqQueryParser.cs index 86c1410099e..a7f10082b43 100644 --- a/src/NHibernate/Linq/NhRelinqQueryParser.cs +++ b/src/NHibernate/Linq/NhRelinqQueryParser.cs @@ -75,7 +75,14 @@ public NHibernateNodeTypeProvider() new[] { ReflectionHelper.GetMethodDefinition(() => LinqExtensionMethods.Timeout(null, 0)), - }, typeof (TimeoutExpressionNode) + }, typeof(TimeoutExpressionNode) + ); + + methodInfoRegistry.Register( + new[] + { + ReflectionHelper.GetMethodDefinition(() => LinqExtensionMethods.AsReadOnly(null)), + }, typeof(AsReadOnlyExpressionNode) ); var nodeTypeProvider = ExpressionTreeParser.CreateDefaultNodeTypeProvider(); @@ -100,7 +107,8 @@ public System.Type GetNodeType(MethodInfo method) public class AsQueryableExpressionNode : MethodCallExpressionNodeBase { - public AsQueryableExpressionNode(MethodCallExpressionParseInfo parseInfo) : base(parseInfo) + public AsQueryableExpressionNode(MethodCallExpressionParseInfo parseInfo) + : base(parseInfo) { } @@ -120,7 +128,8 @@ public class CacheableExpressionNode : ResultOperatorExpressionNodeBase private readonly MethodCallExpressionParseInfo _parseInfo; private readonly ConstantExpression _data; - public CacheableExpressionNode(MethodCallExpressionParseInfo parseInfo, ConstantExpression data) : base(parseInfo, null, null) + public CacheableExpressionNode(MethodCallExpressionParseInfo parseInfo, ConstantExpression data) + : base(parseInfo, null, null) { _parseInfo = parseInfo; _data = data; @@ -168,6 +177,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 412f1c8fabe..e23981fce70 100644 --- a/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs +++ b/src/NHibernate/Linq/ReWriters/QueryReferenceExpressionFlattener.cs @@ -17,6 +17,7 @@ public class QueryReferenceExpressionFlattener : ExpressionTreeVisitor { typeof (CacheableResultOperator), typeof (TimeoutResultOperator), + typeof (AsReadOnlyResultOperator), typeof (FetchOneRequest), typeof (FetchManyRequest) }; diff --git a/src/NHibernate/Linq/ReWriters/ResultOperatorRewriter.cs b/src/NHibernate/Linq/ReWriters/ResultOperatorRewriter.cs index fe1e50f7c40..493c5916fdc 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 a83d3911684..98abdf1f9a2 100644 --- a/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs +++ b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs @@ -124,6 +124,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..8441cb37a59 --- /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 851c1a7fc6d..b741181a3cd 100644 --- a/src/NHibernate/NHibernate.csproj +++ b/src/NHibernate/NHibernate.csproj @@ -334,6 +334,7 @@ +