diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index 7fa41dd1a..66554f147 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -17,6 +17,10 @@ internal record struct QueryKey(object Key, int MetadataToken, ModuleHandle Modu internal class CompiledQueryRunner { + private record struct ClosureTypeInfo(Type ParameterType, PropertyInfo ValueMemberInfo, FieldInfo[] Fields); + + private static readonly ExtendedExpressionReplacer NoopReplacer = new(e => e); + private readonly Domain domain; private readonly Session session; private readonly QueryEndpoint endpoint; @@ -152,47 +156,47 @@ private void AllocateParameterAndReplacer() { if (queryTarget == null) { queryParameter = null; - queryParameterReplacer = new ExtendedExpressionReplacer(e => e); + queryParameterReplacer = NoopReplacer; return; } var closureType = queryTarget.GetType(); - var parameterType = WellKnownOrmTypes.ParameterOfT.CachedMakeGenericType(closureType); - var valueMemberInfo = parameterType.GetProperty(nameof(Parameter.Value), closureType); - queryParameter = (Parameter) System.Activator.CreateInstance(parameterType, "pClosure"); + var info = Memoizer.Get(closureType, static ct => { + var parameterType = WellKnownOrmTypes.ParameterOfT.CachedMakeGenericType(ct); + return new ClosureTypeInfo( + parameterType, + parameterType.GetProperty(nameof(Parameter.Value), ct), + ct.IsClosure() ? ct.GetFields() : null + ); + }, 10_000); + MemberExpression closureAccessor = null; + queryParameter = (Parameter) System.Activator.CreateInstance(info.ParameterType, "pClosure"); queryParameterReplacer = new ExtendedExpressionReplacer(expression => { if (expression.NodeType == ExpressionType.Constant) { - if ((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 (((ConstantExpression)expression).Value is null) { + return null; } - if (closureType.DeclaringType==null) { - if (expression.Type.IsAssignableFrom(closureType)) - return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo); + var expressionType = expression.Type; + if (expressionType.IsClosure()) { + return expressionType == closureType + ? GetClosureAccessor() + : throw new NotSupportedException(string.Format(Strings.ExExpressionDefinedOutsideOfCachingQueryClosure, expression)); } - 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); - } + + if (expressionType.IsAssignableFrom(closureType)) + return GetClosureAccessor(); + if (expressionType.IsAssignableFrom(closureType.DeclaringType) + && info.Fields?.FirstOrDefault(field => field.FieldType == expressionType) is { } memberInfo) { + return Expression.MakeMemberAccess(GetClosureAccessor(), memberInfo); } } + return null; }); + + MemberExpression GetClosureAccessor() => + closureAccessor ??= Expression.MakeMemberAccess(Expression.Constant(queryParameter, info.ParameterType), info.ValueMemberInfo); } private ParameterizedQuery GetCachedQuery() => diff --git a/Orm/Xtensive.Orm/Reflection/TypeHelper.cs b/Orm/Xtensive.Orm/Reflection/TypeHelper.cs index f03577751..ad1dcfda2 100644 --- a/Orm/Xtensive.Orm/Reflection/TypeHelper.cs +++ b/Orm/Xtensive.Orm/Reflection/TypeHelper.cs @@ -1188,9 +1188,11 @@ public static bool IsAnonymous(this Type type) /// public static bool IsClosure(this Type type) { + if (type.BaseType != WellKnownTypes.Object) { + return false; + } var typeName = type.Name; - return type.BaseType == WellKnownTypes.Object - && (typeName.StartsWith("<>", StringComparison.Ordinal) || typeName.StartsWith("VB$", StringComparison.Ordinal)) + return (typeName.StartsWith("<>", StringComparison.Ordinal) || typeName.StartsWith("VB$", StringComparison.Ordinal)) && typeName.IndexOf("DisplayClass", StringComparison.Ordinal) >= 0 && type.IsDefined(CompilerGeneratedAttributeType, false); } @@ -1261,18 +1263,6 @@ internal static bool IsValueTuple(this Type type) #region Private \ internal methods - /// - /// Gets information about field in closure. - /// - /// Closure type. - /// Type of field in closure. - /// If field of exists in closure then returns - /// of that field, otherwise, . - internal static MemberInfo TryGetFieldInfoFromClosure(this Type closureType, Type fieldType) => - closureType.IsClosure() - ? closureType.GetFields().FirstOrDefault(field => field.FieldType == fieldType) - : null; - private static string TrimGenericSuffix(string @string) { var backtickPosition = @string.IndexOf('`');