@@ -21,9 +21,6 @@ namespace Xtensive.Orm.Internals
2121{
2222 internal class CompiledQueryRunner
2323 {
24- private static readonly Func < FieldInfo , IReadOnlySet < Type > , bool > IsFieldReadyToCache = ( fieldInfo , supportedTypes ) =>
25- IsTypeCacheable ( fieldInfo . FieldType , supportedTypes ) ;
26-
2724 private readonly Domain domain ;
2825 private readonly Session session ;
2926 private readonly QueryEndpoint endpoint ;
@@ -34,6 +31,7 @@ internal class CompiledQueryRunner
3431
3532 private Parameter queryParameter ;
3633 private ExtendedExpressionReplacer queryParameterReplacer ;
34+ private Type queryTargetType ;
3735
3836 public QueryResult < TElement > ExecuteCompiled < TElement > ( Func < QueryEndpoint , IQueryable < TElement > > query )
3937 {
@@ -114,24 +112,24 @@ private DelayedQuery<TElement> CreateDelayedSequenceQuery<TElement>(
114112 private ParameterizedQuery GetScalarQuery < TResult > (
115113 Func < QueryEndpoint , TResult > query , bool executeAsSideEffect , out TResult result )
116114 {
117- var cacheable = AllocateParameterAndReplacer ( ) ;
115+ AllocateParameterAndReplacer ( ) ;
118116
119117 var parameterContext = new ParameterContext ( outerContext ) ;
120118 parameterContext . SetValue ( queryParameter , queryTarget ) ;
121119 var scope = new CompiledQueryProcessingScope (
122120 queryParameter , queryParameterReplacer , parameterContext , executeAsSideEffect ) ;
121+
123122 using ( scope . Enter ( ) ) {
124123 result = query . Invoke ( endpoint ) ;
125124 }
126125
127126 var parameterizedQuery = ( ParameterizedQuery ) scope . ParameterizedQuery ;
128- if ( parameterizedQuery == null && queryTarget != null ) {
127+ if ( parameterizedQuery == null && queryTarget != null ) {
129128 throw new NotSupportedException ( Strings . ExNonLinqCallsAreNotSupportedWithinQueryExecuteDelayed ) ;
130129 }
131130
132- if ( cacheable ) {
133- PutCachedQuery ( parameterizedQuery ) ;
134- }
131+ PutQueryToCacheIfAllowed ( parameterizedQuery ) ;
132+
135133 return parameterizedQuery ;
136134 }
137135
@@ -143,29 +141,28 @@ private ParameterizedQuery GetSequenceQuery<TElement>(
143141 return parameterizedQuery ;
144142 }
145143
146- var cacheable = AllocateParameterAndReplacer ( ) ;
144+ AllocateParameterAndReplacer ( ) ;
147145 var scope = new CompiledQueryProcessingScope ( queryParameter , queryParameterReplacer ) ;
148146 using ( scope . Enter ( ) ) {
149147 var result = query . Invoke ( endpoint ) ;
150148 var translatedQuery = endpoint . Provider . Translate ( result . Expression ) ;
151149 parameterizedQuery = ( ParameterizedQuery ) translatedQuery ;
152150 }
153151
154- if ( cacheable ) {
155- PutCachedQuery ( parameterizedQuery ) ;
156- }
152+ PutQueryToCacheIfAllowed ( parameterizedQuery ) ;
153+
157154 return parameterizedQuery ;
158155 }
159156
160- private bool AllocateParameterAndReplacer ( )
157+ private void AllocateParameterAndReplacer ( )
161158 {
162159 if ( queryTarget == null ) {
163160 queryParameter = null ;
164161 queryParameterReplacer = new ExtendedExpressionReplacer ( e => e ) ;
165- return true ;
162+ return ;
166163 }
167164
168- var closureType = queryTarget . GetType ( ) ;
165+ var closureType = queryTargetType = queryTarget . GetType ( ) ;
169166 var parameterType = WellKnownOrmTypes . ParameterOfT . CachedMakeGenericType ( closureType ) ;
170167 var valueMemberInfo = parameterType . GetProperty ( nameof ( Parameter < object > . Value ) , closureType ) ;
171168 queryParameter = ( Parameter ) System . Activator . CreateInstance ( parameterType , "pClosure" ) ;
@@ -202,10 +199,24 @@ private bool AllocateParameterAndReplacer()
202199 }
203200 return null ;
204201 } ) ;
202+ }
203+
204+ private bool IsQueryCacheable ( )
205+ {
206+ if ( queryTargetType == null ) {
207+ return true ;
208+ }
205209
210+ if ( ! queryTargetType . IsClosure ( ) ) {
211+ return true ;
212+ }
206213
207- return ! TypeHelper . IsClosure ( closureType )
208- || closureType . GetFields ( ) . All ( f => IsFieldReadyToCache ( f , supportedTypes ) ) ;
214+ foreach ( var field in queryTargetType . GetFields ( ) ) {
215+ if ( ! IsTypeCacheable ( field . FieldType , supportedTypes ) ) {
216+ return false ;
217+ }
218+ }
219+ return true ;
209220 }
210221
211222 private static bool IsTypeCacheable ( Type type , IReadOnlySet < Type > supportedTypes )
@@ -240,10 +251,7 @@ private static bool IsTypeCacheable(Type type, IReadOnlySet<Type> supportedTypes
240251 TypeCode . Char => true ,
241252 TypeCode . String => true ,
242253 TypeCode . DateTime => true ,
243- TypeCode . Object => type1 == WellKnownTypes . Guid
244- || type1 == WellKnownTypes . TimeSpan
245- || type1 == WellKnownTypes . DateTimeOffset
246- || supportedTypes . Contains ( type1 ) ,
254+ TypeCode . Object => type1 . IsValueType ,
247255 _ => false
248256 } ;
249257 }
@@ -252,8 +260,17 @@ private static bool IsTypeCacheable(Type type, IReadOnlySet<Type> supportedTypes
252260 private ParameterizedQuery GetCachedQuery ( ) =>
253261 domain . QueryCache . TryGetItem ( queryKey , true , out var item ) ? item . Second : null ;
254262
255- private void PutCachedQuery ( ParameterizedQuery parameterizedQuery ) =>
256- domain . QueryCache . Add ( new Pair < object , ParameterizedQuery > ( queryKey , parameterizedQuery ) ) ;
263+ private void PutQueryToCacheIfAllowed ( ParameterizedQuery parameterizedQuery ) {
264+ if ( IsQueryCacheable ( ) ) {
265+ domain . QueryCache . Add ( new Pair < object , ParameterizedQuery > ( queryKey , parameterizedQuery ) ) ;
266+ }
267+ else {
268+ // no .resx used because it is hot path.
269+ if ( OrmLog . IsLogged ( Logging . LogLevel . Info ) )
270+ OrmLog . Info ( "Query can't be cached because closure type it has references to captures reference" +
271+ " type instances. This will lead to long-living objects in memory." ) ;
272+ }
273+ }
257274
258275 private ParameterContext CreateParameterContext ( ParameterizedQuery query )
259276 {
0 commit comments