diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryProcessingScope.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryProcessingScope.cs index fa0dde9aeb..e06bd664a8 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryProcessingScope.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryProcessingScope.cs @@ -13,7 +13,7 @@ namespace Xtensive.Orm.Internals { internal sealed class CompiledQueryProcessingScope { - private TranslatedQuery parameterizedQuery; + private ParameterizedQuery parameterizedQuery; public Parameter QueryParameter { get; } public ExtendedExpressionReplacer QueryParameterReplacer { get; } @@ -43,7 +43,7 @@ public ThreadBindingToken(CompiledQueryProcessingScope scope) public ThreadBindingToken Enter() => new ThreadBindingToken(this); /// Second attempt to set this property. - public TranslatedQuery ParameterizedQuery { + public ParameterizedQuery ParameterizedQuery { get => parameterizedQuery; set { if (parameterizedQuery != null) { diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index 585449d9e6..1eed8ec37d 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2021 Xtensive LLC. +// Copyright (C) 2012-2022 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov @@ -7,6 +7,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xtensive.Caching; @@ -19,6 +20,8 @@ namespace Xtensive.Orm.Internals { internal class CompiledQueryRunner { + private static readonly Func FieldIsSimple = fieldInfo => TypeIsSimple(fieldInfo.FieldType); + private readonly Domain domain; private readonly Session session; private readonly QueryEndpoint endpoint; @@ -44,7 +47,7 @@ public QueryResult ExecuteCompiled(Func(Func query) { var parameterizedQuery = GetCachedQuery(); - if (parameterizedQuery!=null) { + if (parameterizedQuery != null) { return parameterizedQuery.ExecuteScalar(session, CreateParameterContext(parameterizedQuery)); } @@ -65,12 +68,12 @@ public Task> ExecuteCompiledAsync( public Task> ExecuteCompiledAsync( Func> query, CancellationToken token) => - ExecuteCompiledAsync((Func>)query, token); + ExecuteCompiledAsync((Func>) query, token); public Task ExecuteCompiledAsync(Func query, CancellationToken token) { var parameterizedQuery = GetCachedQuery(); - if (parameterizedQuery!=null) { + if (parameterizedQuery != null) { token.ThrowIfCancellationRequested(); return parameterizedQuery.ExecuteScalarAsync(session, CreateParameterContext(parameterizedQuery), token); } @@ -108,7 +111,7 @@ private DelayedQuery CreateDelayedSequenceQuery( private ParameterizedQuery GetScalarQuery( Func query, bool executeAsSideEffect, out TResult result) { - AllocateParameterAndReplacer(); + var cacheable = AllocateParameterAndReplacer(); var parameterContext = new ParameterContext(outerContext); parameterContext.SetValue(queryParameter, queryTarget); @@ -118,12 +121,14 @@ private ParameterizedQuery GetScalarQuery( result = query.Invoke(endpoint); } - var parameterizedQuery = (ParameterizedQuery) scope.ParameterizedQuery; - if (parameterizedQuery==null && queryTarget!=null) { + var parameterizedQuery = scope.ParameterizedQuery; + if (parameterizedQuery == null && queryTarget != null) { throw new NotSupportedException(Strings.ExNonLinqCallsAreNotSupportedWithinQueryExecuteDelayed); } - PutCachedQuery(parameterizedQuery); + if (cacheable) { + PutCachedQuery(parameterizedQuery); + } return parameterizedQuery; } @@ -131,11 +136,11 @@ private ParameterizedQuery GetSequenceQuery( Func> query) { var parameterizedQuery = GetCachedQuery(); - if (parameterizedQuery!=null) { + if (parameterizedQuery != null) { return parameterizedQuery; } - AllocateParameterAndReplacer(); + var cacheable = AllocateParameterAndReplacer(); var scope = new CompiledQueryProcessingScope(queryParameter, queryParameterReplacer); using (scope.Enter()) { var result = query.Invoke(endpoint); @@ -143,16 +148,19 @@ private ParameterizedQuery GetSequenceQuery( parameterizedQuery = (ParameterizedQuery) translatedQuery; } - PutCachedQuery(parameterizedQuery); + if (cacheable) { + PutCachedQuery(parameterizedQuery); + } return parameterizedQuery; } - private void AllocateParameterAndReplacer() + // Returns true is query is cacheable (target contains only simple-type captured vars). + private bool AllocateParameterAndReplacer() { if (queryTarget == null) { queryParameter = null; queryParameterReplacer = new ExtendedExpressionReplacer(e => e); - return; + return true; } var closureType = queryTarget.GetType(); @@ -160,38 +168,43 @@ private void AllocateParameterAndReplacer() var valueMemberInfo = parameterType.GetProperty(nameof(Parameter.Value), closureType); queryParameter = (Parameter) System.Activator.CreateInstance(parameterType, "pClosure"); queryParameterReplacer = new ExtendedExpressionReplacer(expression => { - if (expression.NodeType == ExpressionType.Constant) { - if ((expression as ConstantExpression).Value == null) { + if (expression.NodeType != ExpressionType.Constant || (expression as ConstantExpression).Value == null) { return null; } - if (expression.Type.IsClosure()) { - if (expression.Type==closureType) { - return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo); - } - else { - throw new NotSupportedException(string.Format( - Strings.ExExpressionDefinedOutsideOfCachingQueryClosure, expression)); - } - } - - if (closureType.DeclaringType==null) { - if (expression.Type.IsAssignableFrom(closureType)) - return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo); - } - else { - if (expression.Type.IsAssignableFrom(closureType)) - return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo); - if (expression.Type.IsAssignableFrom(closureType.DeclaringType)) { - var memberInfo = closureType.TryGetFieldInfoFromClosure(expression.Type); - if (memberInfo != null) - return Expression.MakeMemberAccess( - Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo), - memberInfo); - } - } + + var expressionType = expression.Type; + if (expressionType.IsClosure()) { + return expressionType == closureType + ? Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo) + : throw new NotSupportedException(string.Format(Strings.ExExpressionDefinedOutsideOfCachingQueryClosure, expression)); } - return null; + + return expressionType.IsAssignableFrom(closureType) + ? Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo) + : closureType.DeclaringType != null + && expressionType.IsAssignableFrom(closureType.DeclaringType) + && closureType.TryGetFieldInfoFromClosure(expressionType) is MemberInfo memberInfo + ? Expression.MakeMemberAccess(Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo), memberInfo) + : null; }); + return !closureType.Name.Contains("<>c__DisplayClass") // 'DisplayClass' is generated class for captured objects + || closureType.GetFields().All(FieldIsSimple); + } + + private static bool TypeIsSimple(Type type) + { + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsGenericType) { + var genericDef = typeInfo.GetGenericTypeDefinition(); + return (genericDef == WellKnownTypes.NullableOfT || genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyList)) + && TypeIsSimple(typeInfo.GetGenericArguments()[0]); + } + else if (typeInfo.IsArray) { + return TypeIsSimple(typeInfo.GetElementType()); + } + else { + return typeInfo.IsPrimitive || typeInfo.IsEnum || type == WellKnownTypes.String || type == WellKnownTypes.Decimal; + } } private ParameterizedQuery GetCachedQuery() => @@ -203,7 +216,7 @@ private void PutCachedQuery(ParameterizedQuery parameterizedQuery) => private ParameterContext CreateParameterContext(ParameterizedQuery query) { var parameterContext = new ParameterContext(outerContext); - if (query.QueryParameter!=null) { + if (query.QueryParameter != null) { parameterContext.SetValue(query.QueryParameter, queryTarget); } diff --git a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs index d8378b5a7d..01bb7edb12 100644 --- a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs @@ -3,6 +3,7 @@ // See the License.txt file in the project root for more information. using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -73,6 +74,7 @@ internal static class WellKnownTypes public static readonly Type ByteArray = typeof(byte[]); public static readonly Type ObjectArray = typeof(object[]); + public static readonly Type IReadOnlyList = typeof(IReadOnlyList<>); public static readonly Type DefaultMemberAttribute = typeof(DefaultMemberAttribute); }