internal ObjectQueryExecutionPlan GetExecutionPlan( MergeOption mergeOption, bool useCSharpNullComparisonBehavior) { ObjectQueryExecutionPlan queryExecutionPlan; this._plans.TryGetValue(CompiledQueryCacheEntry.GenerateLocalCacheKey(mergeOption, useCSharpNullComparisonBehavior), out queryExecutionPlan); return(queryExecutionPlan); }
internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMergeOption) { Debug.Assert(Span == null, "Include span specified on compiled LINQ-based ObjectQuery instead of within the expression tree?"); Debug.Assert(_cachedPlan == null, "Cached plan should not be set on compiled LINQ queries"); ObjectQueryExecutionPlan plan = null; var cacheEntry = _cacheEntry; var useCSharpNullComparisonBehavior = ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior; if (cacheEntry != null) { // The cache entry has already been retrieved, so compute the effective merge option with the following precedence: // 1. The merge option specified as the argument to Execute(MergeOption), and so to this method // 2. The merge option set using ObjectQuery.MergeOption // 3. The propagated merge option as recorded in the cache entry // 4. The global default merge option. var mergeOption = EnsureMergeOption(forMergeOption, UserSpecifiedMergeOption, cacheEntry.PropagatedMergeOption); // Ask for the corresponding execution plan plan = cacheEntry.GetExecutionPlan(mergeOption, useCSharpNullComparisonBehavior); if (plan == null) { // Convert the LINQ expression to produce a command tree var converter = CreateExpressionConverter(); var queryExpression = converter.Convert(); var parameters = converter.GetParameters(); // Prepare the execution plan using the command tree and the computed effective merge option var tree = DbQueryCommandTree.FromValidExpression( ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !useCSharpNullComparisonBehavior); plan = _objectQueryExecutionPlanFactory.Prepare( ObjectContext, tree, ElementType, mergeOption, EffectiveStreamingBehaviour, converter.PropagatedSpan, parameters, converter.AliasGenerator); // Update and retrieve the execution plan plan = cacheEntry.SetExecutionPlan(plan, useCSharpNullComparisonBehavior); } } else { // This instance does not yet have a reference to a cache entry. // First, attempt to retrieve an existing cache entry. var cacheManager = ObjectContext.MetadataWorkspace.GetQueryCacheManager(); var cacheKey = new CompiledQueryCacheKey(_cacheToken); if (cacheManager.TryCacheLookup(cacheKey, out cacheEntry)) { // An entry was found in the cache, so compute the effective merge option based on its propagated merge option, // and use the UseCSharpNullComparisonBehavior flag to retrieve the corresponding execution plan. _cacheEntry = cacheEntry; var mergeOption = EnsureMergeOption(forMergeOption, UserSpecifiedMergeOption, cacheEntry.PropagatedMergeOption); plan = cacheEntry.GetExecutionPlan(mergeOption, useCSharpNullComparisonBehavior); } // 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. if (plan == null) { // The execution plan needs to be produced, so create an appropriate expression converter and generate the query command tree. var converter = CreateExpressionConverter(); var queryExpression = converter.Convert(); var parameters = converter.GetParameters(); var tree = DbQueryCommandTree.FromValidExpression( ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !useCSharpNullComparisonBehavior); // If a cache entry for this compiled query's cache key was not successfully retrieved, then it must be created now. // Note that this is only possible after converting the LINQ expression and discovering the propagated merge option, // which is required in order to create the cache entry. if (cacheEntry == null) { // Create the cache entry using this instance's cache token and the propagated merge option (which may be null) cacheEntry = new CompiledQueryCacheEntry(cacheKey, converter.PropagatedMergeOption); // Attempt to add the entry to the cache. If an entry was added in the meantime, use that entry instead. QueryCacheEntry foundEntry; if (cacheManager.TryLookupAndAdd(cacheEntry, out foundEntry)) { cacheEntry = (CompiledQueryCacheEntry)foundEntry; } // We now have a cache entry, so hold onto it for future use. _cacheEntry = cacheEntry; } // Recompute the effective merge option in case a cache entry was just constructed above var mergeOption = EnsureMergeOption(forMergeOption, UserSpecifiedMergeOption, cacheEntry.PropagatedMergeOption); // Ask the (retrieved or constructed) cache entry for the corresponding execution plan. plan = cacheEntry.GetExecutionPlan(mergeOption, useCSharpNullComparisonBehavior); if (plan == null) { // The plan is not present, so prepare it now using the computed effective merge option plan = _objectQueryExecutionPlanFactory.Prepare( ObjectContext, tree, ElementType, mergeOption, EffectiveStreamingBehaviour, converter.PropagatedSpan, parameters, converter.AliasGenerator); // Update the execution plan on the cache entry. // If the execution plan was set in the meantime, SetExecutionPlan will return that value, otherwise it will return 'plan'. plan = cacheEntry.SetExecutionPlan(plan, useCSharpNullComparisonBehavior); } } } // Get parameters from the plan and set them. var currentParams = EnsureParameters(); if (plan.CompiledQueryParameters != null && plan.CompiledQueryParameters.Any()) { currentParams.SetReadOnly(false); currentParams.Clear(); foreach (var pair in plan.CompiledQueryParameters) { // Parameters retrieved from the CompiledQueryParameters collection must be cloned before being added to the query. // The cached plan is shared and when used in multithreaded scenarios failing to clone the parameter would result // in the code below updating the values of shared parameter instances saved in the cached plan and used by all // queries using that plan, regardless of the values they were actually invoked with, causing incorrect results // when those queries were later executed. // var convertedParam = pair.Item1.ShallowCopy(); var parameterExpression = pair.Item2; currentParams.Add(convertedParam); if (parameterExpression != null) { convertedParam.Value = parameterExpression.EvaluateParameter(_parameterValues); } } } currentParams.SetReadOnly(true); Debug.Assert(plan != null, "Failed to produce an execution plan?"); return plan; }
internal ObjectQueryExecutionPlan SetExecutionPlan( ObjectQueryExecutionPlan newPlan, bool useCSharpNullComparisonBehavior) { return(this._plans.GetOrAdd(CompiledQueryCacheEntry.GenerateLocalCacheKey(newPlan.MergeOption, useCSharpNullComparisonBehavior), newPlan)); }