1
//---------------------------------------------------------------------
2 // <copyright file="CompiledELinqQueryState.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System
.Data
.Objects
.ELinq
13 using System
.Collections
.Generic
;
14 using System
.Collections
.ObjectModel
;
15 using System
.Data
.Common
.CommandTrees
;
16 using System
.Data
.Common
.QueryCache
;
17 using System
.Data
.Metadata
.Edm
;
18 using System
.Data
.Objects
;
19 using System
.Data
.Objects
.Internal
;
20 using System
.Diagnostics
;
22 using System
.Linq
.Expressions
;
25 /// Models a compiled Linq to Entities ObjectQuery
27 internal sealed class CompiledELinqQueryState
: ELinqQueryState
29 private readonly Guid _cacheToken
;
30 private readonly object[] _parameterValues
;
31 private CompiledQueryCacheEntry _cacheEntry
;
34 /// Factory method to create a new compiled query state instance
36 /// <param name="elementType">The element type of the new instance (the 'T' of the ObjectQuery<T> that the new state instance will back)"</param>
37 /// <param name="context">The object context with which the new instance should be associated</param>
38 /// <param name="lambda">The compiled query definition, as a <see cref="LambdaExpression"/></param>
39 /// <param name="cacheToken">The cache token to use when retrieving or storing the new instance's execution plan in the query cache</param>
40 /// <param name="parameterValues">The values passed into the CompiledQuery delegate</param>
41 internal CompiledELinqQueryState(Type elementType
, ObjectContext context
, LambdaExpression lambda
, Guid cacheToken
, object[] parameterValues
)
42 : base(elementType
, context
, lambda
)
44 EntityUtil
.CheckArgumentNull(parameterValues
, "parameterValues");
46 _cacheToken
= cacheToken
;
47 _parameterValues
= parameterValues
;
49 this.EnsureParameters();
50 this.Parameters
.SetReadOnly(true);
53 internal override ObjectQueryExecutionPlan
GetExecutionPlan(MergeOption
? forMergeOption
)
55 Debug
.Assert(this.Span
== null, "Include span specified on compiled LINQ-based ObjectQuery instead of within the expression tree?");
56 Debug
.Assert(this._cachedPlan
== null, "Cached plan should not be set on compiled LINQ queries");
58 // Metadata is required to generate the execution plan or to retrieve it from the cache.
59 this.ObjectContext
.EnsureMetadata();
61 ObjectQueryExecutionPlan plan
= null;
62 CompiledQueryCacheEntry cacheEntry
= this._cacheEntry
;
63 bool useCSharpNullComparisonBehavior
= this.ObjectContext
.ContextOptions
.UseCSharpNullComparisonBehavior
;
64 if (cacheEntry
!= null)
66 // The cache entry has already been retrieved, so compute the effective merge option with the following precedence:
67 // 1. The merge option specified as the argument to Execute(MergeOption), and so to this method
68 // 2. The merge option set using ObjectQuery.MergeOption
69 // 3. The propagated merge option as recorded in the cache entry
70 // 4. The global default merge option.
71 MergeOption mergeOption
= EnsureMergeOption(forMergeOption
, this.UserSpecifiedMergeOption
, cacheEntry
.PropagatedMergeOption
);
73 // Ask for the corresponding execution plan
74 plan
= cacheEntry
.GetExecutionPlan(mergeOption
, useCSharpNullComparisonBehavior
);
77 // Convert the LINQ expression to produce a command tree
78 ExpressionConverter converter
= this.CreateExpressionConverter();
79 DbExpression queryExpression
= converter
.Convert();
80 ReadOnlyCollection
<KeyValuePair
<ObjectParameter
, QueryParameterExpression
>> parameters
= converter
.GetParameters();
82 // Prepare the execution plan using the command tree and the computed effective merge option
83 DbQueryCommandTree tree
= DbQueryCommandTree
.FromValidExpression(this.ObjectContext
.MetadataWorkspace
, DataSpace
.CSpace
, queryExpression
);
84 plan
= ObjectQueryExecutionPlan
.Prepare(this.ObjectContext
, tree
, this.ElementType
, mergeOption
, converter
.PropagatedSpan
, parameters
, converter
.AliasGenerator
);
86 // Update and retrieve the execution plan
87 plan
= cacheEntry
.SetExecutionPlan(plan
, useCSharpNullComparisonBehavior
);
92 // This instance does not yet have a reference to a cache entry.
93 // First, attempt to retrieve an existing cache entry.
94 QueryCacheManager cacheManager
= this.ObjectContext
.MetadataWorkspace
.GetQueryCacheManager();
95 CompiledQueryCacheKey cacheKey
= new CompiledQueryCacheKey(this._cacheToken
);
97 if (cacheManager
.TryCacheLookup(cacheKey
, out cacheEntry
))
99 // An entry was found in the cache, so compute the effective merge option based on its propagated merge option,
100 // and use the UseCSharpNullComparisonBehavior flag to retrieve the corresponding execution plan.
101 this._cacheEntry
= cacheEntry
;
102 MergeOption mergeOption
= EnsureMergeOption(forMergeOption
, this.UserSpecifiedMergeOption
, cacheEntry
.PropagatedMergeOption
);
103 plan
= cacheEntry
.GetExecutionPlan(mergeOption
, useCSharpNullComparisonBehavior
);
106 // If no cache entry was found or if the cache entry did not contain the required execution plan, the plan is still null at this point.
109 // The execution plan needs to be produced, so create an appropriate expression converter and generate the query command tree.
110 ExpressionConverter converter
= this.CreateExpressionConverter();
111 DbExpression queryExpression
= converter
.Convert();
112 ReadOnlyCollection
<KeyValuePair
<ObjectParameter
, QueryParameterExpression
>> parameters
= converter
.GetParameters();
113 DbQueryCommandTree tree
= DbQueryCommandTree
.FromValidExpression(this.ObjectContext
.MetadataWorkspace
, DataSpace
.CSpace
, queryExpression
);
115 // If a cache entry for this compiled query's cache key was not successfully retrieved, then it must be created now.
116 // Note that this is only possible after converting the LINQ expression and discovering the propagated merge option,
117 // which is required in order to create the cache entry.
118 if (cacheEntry
== null)
120 // Create the cache entry using this instance's cache token and the propagated merge option (which may be null)
121 cacheEntry
= new CompiledQueryCacheEntry(cacheKey
, converter
.PropagatedMergeOption
);
123 // Attempt to add the entry to the cache. If an entry was added in the meantime, use that entry instead.
124 QueryCacheEntry foundEntry
;
125 if (cacheManager
.TryLookupAndAdd(cacheEntry
, out foundEntry
))
127 cacheEntry
= (CompiledQueryCacheEntry
)foundEntry
;
130 // We now have a cache entry, so hold onto it for future use.
131 this._cacheEntry
= cacheEntry
;
134 // Recompute the effective merge option in case a cache entry was just constructed above
135 MergeOption mergeOption
= EnsureMergeOption(forMergeOption
, this.UserSpecifiedMergeOption
, cacheEntry
.PropagatedMergeOption
);
137 // Ask the (retrieved or constructed) cache entry for the corresponding execution plan.
138 plan
= cacheEntry
.GetExecutionPlan(mergeOption
, useCSharpNullComparisonBehavior
);
141 // The plan is not present, so prepare it now using the computed effective merge option
142 plan
= ObjectQueryExecutionPlan
.Prepare(this.ObjectContext
, tree
, this.ElementType
, mergeOption
, converter
.PropagatedSpan
, parameters
, converter
.AliasGenerator
);
144 // Update the execution plan on the cache entry.
145 // If the execution plan was set in the meantime, SetExecutionPlan will return that value, otherwise it will return 'plan'.
146 plan
= cacheEntry
.SetExecutionPlan(plan
, useCSharpNullComparisonBehavior
);
151 // Get parameters from the plan and set them.
152 ObjectParameterCollection currentParams
= this.EnsureParameters();
153 if (plan
.CompiledQueryParameters
!= null && plan
.CompiledQueryParameters
.Count
> 0)
155 currentParams
.SetReadOnly(false);
156 currentParams
.Clear();
157 foreach (KeyValuePair
<ObjectParameter
, QueryParameterExpression
> pair
in plan
.CompiledQueryParameters
)
159 // Parameters retrieved from the CompiledQueryParameters collection must be cloned before being added to the query.
160 // The cached plan is shared and when used in multithreaded scenarios failing to clone the parameter would result
161 // in the code below updating the values of shared parameter instances saved in the cached plan and used by all
162 // queries using that plan, regardless of the values they were actually invoked with, causing incorrect results
163 // when those queries were later executed.
165 ObjectParameter convertedParam
= pair
.Key
.ShallowCopy();
166 QueryParameterExpression parameterExpression
= pair
.Value
;
167 currentParams
.Add(convertedParam
);
168 if (parameterExpression
!= null)
170 convertedParam
.Value
= parameterExpression
.EvaluateParameter(_parameterValues
);
174 currentParams
.SetReadOnly(true);
176 Debug
.Assert(plan
!= null, "Failed to produce an execution plan?");
181 /// Overrides GetResultType and attempts to first retrieve the result type from the cache entry.
183 /// <returns>The query result type from this compiled query's cache entry, if possible; otherwise defers to <see cref="ELinqQueryState.GetResultType"/></returns>
184 protected override TypeUsage
GetResultType()
186 CompiledQueryCacheEntry cacheEntry
= this._cacheEntry
;
187 TypeUsage resultType
;
188 if (cacheEntry
!= null &&
189 cacheEntry
.TryGetResultType(out resultType
))
194 return base.GetResultType();
198 /// Gets a LINQ expression that defines this query.
199 /// This is overridden to remove parameter references from the underlying expression,
200 /// producing an expression that contains the values of those parameters as <see cref="ConstantExpression"/>s.
202 internal override Expression Expression
206 return CreateDonateableExpressionVisitor
.Replace((LambdaExpression
)base.Expression
, ObjectContext
, _parameterValues
);
211 /// Overrides CreateExpressionConverter to return a converter that uses a binding context based on the compiled query parameters,
212 /// rather than a default binding context.
214 /// <returns>An expression converter appropriate for converting this compiled query state instance</returns>
215 protected override ExpressionConverter
CreateExpressionConverter()
217 LambdaExpression lambda
= (LambdaExpression
)base.Expression
;
218 Funcletizer funcletizer
= Funcletizer
.CreateCompiledQueryEvaluationFuncletizer(this.ObjectContext
, lambda
.Parameters
.First(), lambda
.Parameters
.Skip(1).ToList().AsReadOnly());
219 // Return a new expression converter that uses the initialized command tree and binding context.
220 return new ExpressionConverter(funcletizer
, lambda
.Body
);
224 /// Replaces ParameterExpresion with ConstantExpression
225 /// to make the expression usable as a donor expression
227 private sealed class CreateDonateableExpressionVisitor
: EntityExpressionVisitor
229 private readonly Dictionary
<ParameterExpression
, object> _parameterToValueLookup
;
231 private CreateDonateableExpressionVisitor(Dictionary
<ParameterExpression
, object> parameterToValueLookup
)
233 _parameterToValueLookup
= parameterToValueLookup
;
236 internal static Expression
Replace(LambdaExpression query
, ObjectContext objectContext
, object[] parameterValues
)
238 Dictionary
<ParameterExpression
, object> parameterLookup
= query
241 .Zip(parameterValues
)
242 .ToDictionary(pair
=> pair
.Key
, pair
=> pair
.Value
);
243 parameterLookup
.Add(query
.Parameters
.First(), objectContext
);
244 var replacer
= new CreateDonateableExpressionVisitor(parameterLookup
);
245 return replacer
.Visit(query
.Body
);
248 internal override Expression
VisitParameter(ParameterExpression p
)
252 if (_parameterToValueLookup
.TryGetValue(p
, out value))
254 result
= Expression
.Constant(value, p
.Type
);
258 result
= base.VisitParameter(p
);