Beispiel #1
0
        /// <summary>
        /// Determines equality of two cache keys based on cache context values
        /// </summary>
        public override bool Equals(object otherObject)
        {
            Debug.Assert(null != otherObject, "otherObject must not be null");
            if (typeof(LinqQueryCacheKey) != otherObject.GetType())
            {
                return(false);
            }

            LinqQueryCacheKey otherObjectQueryCacheKey = (LinqQueryCacheKey)otherObject;

            // also use result type...
            return((_parameterCount == otherObjectQueryCacheKey._parameterCount) &&
                   (_mergeOption == otherObjectQueryCacheKey._mergeOption) &&
                   Equals(otherObjectQueryCacheKey._expressionKey, _expressionKey) &&
                   Equals(otherObjectQueryCacheKey._includePathsToken, _includePathsToken) &&
                   Equals(otherObjectQueryCacheKey._parametersToken, _parametersToken) &&
                   Equals(otherObjectQueryCacheKey._resultType, _resultType) &&
                   Equals(otherObjectQueryCacheKey._useCSharpNullComparisonBehavior, _useCSharpNullComparisonBehavior));
        }
        internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption?forMergeOption)
        {
            Debug.Assert(this.Span == null, "Include span specified on compiled LINQ-based ObjectQuery instead of within the expression tree?");

            // If this query has already been prepared, its current execution plan may no longer be valid.
            ObjectQueryExecutionPlan plan = this._cachedPlan;

            if (plan != null)
            {
                // Was a merge option specified in the call to Execute(MergeOption) or set via ObjectQuery.MergeOption?
                MergeOption?explicitMergeOption = GetMergeOption(forMergeOption, this.UserSpecifiedMergeOption);

                // If a merge option was explicitly specified, and it does not match the plan's merge option, then the plan is no longer valid.
                // If the context flag UseCSharpNullComparisonBehavior was modified, then the plan is no longer valid.
                if ((explicitMergeOption.HasValue &&
                     explicitMergeOption.Value != plan.MergeOption) ||
                    this._recompileRequired() ||
                    this.ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior != this._useCSharpNullComparisonBehavior)
                {
                    plan = null;
                }
            }

            // The plan may have been invalidated above, or this query may never have been prepared.
            if (plan == null)
            {
                // Metadata is required to generate the execution plan.
                this.ObjectContext.EnsureMetadata();

                // Reset internal state
                this._recompileRequired = null;
                this.ResetParameters();

                // Translate LINQ expression to a DbExpression
                ExpressionConverter converter       = this.CreateExpressionConverter();
                DbExpression        queryExpression = converter.Convert();

                // This delegate tells us when a part of the expression tree has changed requiring a recompile.
                this._recompileRequired = converter.RecompileRequired;

                // Determine the merge option, with the following precedence:
                // 1. A merge option was specified explicitly as the argument to Execute(MergeOption).
                // 2. The user has set the MergeOption property on the ObjectQuery instance.
                // 3. A merge option has been extracted from the 'root' query and propagated to the root of the expression tree.
                // 4. The global default merge option.
                MergeOption mergeOption = EnsureMergeOption(forMergeOption,
                                                            this.UserSpecifiedMergeOption,
                                                            converter.PropagatedMergeOption);

                this._useCSharpNullComparisonBehavior = this.ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior;

                // If parameters were aggregated from referenced (non-LINQ) ObjectQuery instances then add them to the parameters collection
                _linqParameters = converter.GetParameters();
                if (_linqParameters != null && _linqParameters.Count > 0)
                {
                    ObjectParameterCollection currentParams = this.EnsureParameters();
                    currentParams.SetReadOnly(false);
                    foreach (KeyValuePair <ObjectParameter, QueryParameterExpression> pair in _linqParameters)
                    {
                        // Note that it is safe to add the parameter directly only
                        // because parameters are cloned before they are added to the
                        // converter's parameter collection, or they came from this
                        // instance's parameter collection in the first place.
                        ObjectParameter convertedParam = pair.Key;
                        currentParams.Add(convertedParam);
                    }
                    currentParams.SetReadOnly(true);
                }

                // Try retrieving the execution plan from the global query cache (if plan caching is enabled).
                System.Data.Common.QueryCache.QueryCacheManager cacheManager = null;
                System.Data.Common.QueryCache.LinqQueryCacheKey cacheKey     = null;
                if (this.PlanCachingEnabled && !this._recompileRequired())
                {
                    // Create a new cache key that reflects the current state of the Parameters collection
                    // and the Span object (if any), and uses the specified merge option.
                    string expressionKey;
                    if (ExpressionKeyGen.TryGenerateKey(queryExpression, out expressionKey))
                    {
                        cacheKey = new System.Data.Common.QueryCache.LinqQueryCacheKey(
                            expressionKey,
                            (null == this.Parameters ? 0 : this.Parameters.Count),
                            (null == this.Parameters ? null : this.Parameters.GetCacheKey()),
                            (null == converter.PropagatedSpan ? null : converter.PropagatedSpan.GetCacheKey()),
                            mergeOption,
                            this._useCSharpNullComparisonBehavior,
                            this.ElementType);

                        cacheManager = this.ObjectContext.MetadataWorkspace.GetQueryCacheManager();
                        ObjectQueryExecutionPlan executionPlan = null;
                        if (cacheManager.TryCacheLookup(cacheKey, out executionPlan))
                        {
                            plan = executionPlan;
                        }
                    }
                }

                // If execution plan wasn't retrieved from the cache, build a new one and cache it.
                if (plan == null)
                {
                    DbQueryCommandTree tree = DbQueryCommandTree.FromValidExpression(this.ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression);
                    plan = ObjectQueryExecutionPlan.Prepare(this.ObjectContext, tree, this.ElementType, mergeOption, converter.PropagatedSpan, null, converter.AliasGenerator);

                    // If caching is enabled then update the cache now.
                    // Note: the logic is the same as in EntitySqlQueryState.
                    if (cacheKey != null)
                    {
                        var             newEntry   = new QueryCacheEntry(cacheKey, plan);
                        QueryCacheEntry foundEntry = null;
                        if (cacheManager.TryLookupAndAdd(newEntry, out foundEntry))
                        {
                            // If TryLookupAndAdd returns 'true' then the entry was already present in the cache when the attempt to add was made.
                            // In this case the existing execution plan should be used.
                            plan = (ObjectQueryExecutionPlan)foundEntry.GetTarget();
                        }
                    }
                }

                // Remember the current plan in the local cache, so that we don't have to recalc the key and look into the global cache
                // if the same instance of query gets executed more than once.
                this._cachedPlan = plan;
            }

            // Evaluate parameter values for the query.
            if (_linqParameters != null)
            {
                foreach (KeyValuePair <ObjectParameter, QueryParameterExpression> pair in _linqParameters)
                {
                    ObjectParameter          parameter           = pair.Key;
                    QueryParameterExpression parameterExpression = pair.Value;
                    if (null != parameterExpression)
                    {
                        parameter.Value = parameterExpression.EvaluateParameter(null);
                    }
                }
            }

            return(plan);
        }