1 //---------------------------------------------------------------------
2 // <copyright file="ELinqQueryState.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.Data.Common.CommandTrees;
15 using System.Data.Common.CommandTrees.Internal;
16 using System.Data.Common.QueryCache;
17 using System.Data.Metadata.Edm;
18 using System.Data.Objects;
19 using System.Data.Objects.ELinq;
20 using System.Data.Objects.Internal;
21 using System.Diagnostics;
22 using System.Reflection;
24 using System.Linq.Expressions;
25 using System.Collections.ObjectModel;
28 /// Models a Linq to Entities ObjectQuery
30 internal class ELinqQueryState : ObjectQueryState
34 private readonly Expression _expression;
35 private Func<bool> _recompileRequired;
36 private ReadOnlyCollection<KeyValuePair<ObjectParameter, QueryParameterExpression>> _linqParameters;
37 private bool _useCSharpNullComparisonBehavior;
44 /// Constructs a new <see cref="ELinqQueryState"/> instance based on the specified Linq Expression
45 /// against the specified ObjectContext.
47 /// <param name="elementType">The element type of the implemented ObjectQuery, as a CLR type.</param>
48 /// <param name="context">The ObjectContext with which the implemented ObjectQuery is associated.</param>
49 /// <param name="expression">The Linq Expression that defines this query.</param>
50 internal ELinqQueryState(Type elementType, ObjectContext context, Expression expression)
51 : base(elementType, context, null, null)
54 // Initialize the LINQ expression, which is passed in via
55 // public APIs on ObjectQuery and must be checked here
56 // (the base class performs similar checks on the ObjectContext and MergeOption arguments).
58 EntityUtil.CheckArgumentNull(expression, "expression");
59 // closure bindings and initializers are explicitly allowed to be null
61 _expression = expression;
62 _useCSharpNullComparisonBehavior = context.ContextOptions.UseCSharpNullComparisonBehavior;
66 /// Constructs a new <see cref="ELinqQueryState"/> instance based on the specified Linq Expression,
67 /// copying the state information from the specified ObjectQuery.
69 /// <param name="elementType">The element type of the implemented ObjectQuery, as a CLR type.</param>
70 /// <param name="query">The ObjectQuery from which the state information should be copied.</param>
71 /// <param name="expression">The Linq Expression that defines this query.</param>
72 internal ELinqQueryState(Type elementType, ObjectQuery query, Expression expression)
73 : base(elementType, query)
75 EntityUtil.CheckArgumentNull(expression, "expression");
76 _expression = expression;
81 #region ObjectQueryState overrides
83 protected override TypeUsage GetResultType()
85 // Since this method is only called once, on demand, a full conversion pass
86 // is performed to produce the DbExpression and return its result type.
87 // This does not affect any cached execution plan or closure bindings that may be present.
88 ExpressionConverter converter = this.CreateExpressionConverter();
89 return converter.Convert().ResultType;
92 internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMergeOption)
94 Debug.Assert(this.Span == null, "Include span specified on compiled LINQ-based ObjectQuery instead of within the expression tree?");
96 // If this query has already been prepared, its current execution plan may no longer be valid.
97 ObjectQueryExecutionPlan plan = this._cachedPlan;
100 // Was a merge option specified in the call to Execute(MergeOption) or set via ObjectQuery.MergeOption?
101 MergeOption? explicitMergeOption = GetMergeOption(forMergeOption, this.UserSpecifiedMergeOption);
103 // If a merge option was explicitly specified, and it does not match the plan's merge option, then the plan is no longer valid.
104 // If the context flag UseCSharpNullComparisonBehavior was modified, then the plan is no longer valid.
105 if((explicitMergeOption.HasValue &&
106 explicitMergeOption.Value != plan.MergeOption) ||
107 this._recompileRequired() ||
108 this.ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior != this._useCSharpNullComparisonBehavior)
114 // The plan may have been invalidated above, or this query may never have been prepared.
117 // Metadata is required to generate the execution plan.
118 this.ObjectContext.EnsureMetadata();
120 // Reset internal state
121 this._recompileRequired = null;
122 this.ResetParameters();
124 // Translate LINQ expression to a DbExpression
125 ExpressionConverter converter = this.CreateExpressionConverter();
126 DbExpression queryExpression = converter.Convert();
128 // This delegate tells us when a part of the expression tree has changed requiring a recompile.
129 this._recompileRequired = converter.RecompileRequired;
131 // Determine the merge option, with the following precedence:
132 // 1. A merge option was specified explicitly as the argument to Execute(MergeOption).
133 // 2. The user has set the MergeOption property on the ObjectQuery instance.
134 // 3. A merge option has been extracted from the 'root' query and propagated to the root of the expression tree.
135 // 4. The global default merge option.
136 MergeOption mergeOption = EnsureMergeOption(forMergeOption,
137 this.UserSpecifiedMergeOption,
138 converter.PropagatedMergeOption);
140 this._useCSharpNullComparisonBehavior = this.ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior;
142 // If parameters were aggregated from referenced (non-LINQ) ObjectQuery instances then add them to the parameters collection
143 _linqParameters = converter.GetParameters();
144 if (_linqParameters != null && _linqParameters.Count > 0)
146 ObjectParameterCollection currentParams = this.EnsureParameters();
147 currentParams.SetReadOnly(false);
148 foreach (KeyValuePair<ObjectParameter, QueryParameterExpression> pair in _linqParameters)
150 // Note that it is safe to add the parameter directly only
151 // because parameters are cloned before they are added to the
152 // converter's parameter collection, or they came from this
153 // instance's parameter collection in the first place.
154 ObjectParameter convertedParam = pair.Key;
155 currentParams.Add(convertedParam);
157 currentParams.SetReadOnly(true);
160 // Try retrieving the execution plan from the global query cache (if plan caching is enabled).
161 System.Data.Common.QueryCache.QueryCacheManager cacheManager = null;
162 System.Data.Common.QueryCache.LinqQueryCacheKey cacheKey = null;
163 if (this.PlanCachingEnabled && !this._recompileRequired())
165 // Create a new cache key that reflects the current state of the Parameters collection
166 // and the Span object (if any), and uses the specified merge option.
167 string expressionKey;
168 if (ExpressionKeyGen.TryGenerateKey(queryExpression, out expressionKey))
170 cacheKey = new System.Data.Common.QueryCache.LinqQueryCacheKey(
172 (null == this.Parameters ? 0 : this.Parameters.Count),
173 (null == this.Parameters ? null : this.Parameters.GetCacheKey()),
174 (null == converter.PropagatedSpan ? null : converter.PropagatedSpan.GetCacheKey()),
176 this._useCSharpNullComparisonBehavior,
179 cacheManager = this.ObjectContext.MetadataWorkspace.GetQueryCacheManager();
180 ObjectQueryExecutionPlan executionPlan = null;
181 if (cacheManager.TryCacheLookup(cacheKey, out executionPlan))
183 plan = executionPlan;
188 // If execution plan wasn't retrieved from the cache, build a new one and cache it.
191 DbQueryCommandTree tree = DbQueryCommandTree.FromValidExpression(this.ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression);
192 plan = ObjectQueryExecutionPlan.Prepare(this.ObjectContext, tree, this.ElementType, mergeOption, converter.PropagatedSpan, null, converter.AliasGenerator);
194 // If caching is enabled then update the cache now.
195 // Note: the logic is the same as in EntitySqlQueryState.
196 if (cacheKey != null)
198 var newEntry = new QueryCacheEntry(cacheKey, plan);
199 QueryCacheEntry foundEntry = null;
200 if (cacheManager.TryLookupAndAdd(newEntry, out foundEntry))
202 // If TryLookupAndAdd returns 'true' then the entry was already present in the cache when the attempt to add was made.
203 // In this case the existing execution plan should be used.
204 plan = (ObjectQueryExecutionPlan)foundEntry.GetTarget();
209 // Remember the current plan in the local cache, so that we don't have to recalc the key and look into the global cache
210 // if the same instance of query gets executed more than once.
211 this._cachedPlan = plan;
214 // Evaluate parameter values for the query.
215 if (_linqParameters != null)
217 foreach (KeyValuePair<ObjectParameter, QueryParameterExpression> pair in _linqParameters)
219 ObjectParameter parameter = pair.Key;
220 QueryParameterExpression parameterExpression = pair.Value;
221 if (null != parameterExpression)
223 parameter.Value = parameterExpression.EvaluateParameter(null);
232 /// Returns a new ObjectQueryState instance with the specified navigation property path specified as an Include span.
233 /// For eLINQ queries the Include operation is modelled as a method call expression applied to the source ObectQuery,
234 /// so the <see cref="Span"/> property is always <c>null</c> on the returned instance.
236 /// <typeparam name="TElementType">The element type of the resulting query</typeparam>
237 /// <param name="sourceQuery">The ObjectQuery on which Include was called; required to build the new method call expression</param>
238 /// <param name="includePath">The new Include path</param>
239 /// <returns>A new ObjectQueryState instance that incorporates the Include path, in this case a new method call expression</returns>
240 internal override ObjectQueryState Include<TElementType>(ObjectQuery<TElementType> sourceQuery, string includePath)
242 MethodInfo includeMethod = sourceQuery.GetType().GetMethod("Include", BindingFlags.Public | BindingFlags.Instance);
243 Debug.Assert(includeMethod != null, "Unable to find ObjectQuery.Include method?");
245 Expression includeCall = Expression.Call(Expression.Constant(sourceQuery), includeMethod, new Expression[] { Expression.Constant(includePath, typeof(string)) });
246 ObjectQueryState retState = new ELinqQueryState(this.ElementType, this.ObjectContext, includeCall);
247 this.ApplySettingsTo(retState);
252 /// eLINQ queries do not have command text. This method always returns <c>false</c>.
254 /// <param name="commandText">Always set to <c>null</c></param>
255 /// <returns>Always returns <c>false</c></returns>
256 internal override bool TryGetCommandText(out string commandText)
263 /// Gets the LINQ Expression that defines this query for external (of ObjectQueryState) use.
264 /// Note that the <see cref="Expression"/> property is used, which is overridden by compiled eLINQ
265 /// queries to produce an Expression tree where parameter references have been replaced with constants.
267 /// <param name="expression">The LINQ expression that describes this query</param>
268 /// <returns>Always returns <c>true</c></returns>
269 internal override bool TryGetExpression(out System.Linq.Expressions.Expression expression)
271 expression = this.Expression;
277 internal virtual Expression Expression { get { return _expression; } }
279 protected virtual ExpressionConverter CreateExpressionConverter()
281 Funcletizer funcletizer = Funcletizer.CreateQueryFuncletizer(this.ObjectContext);
282 return new ExpressionConverter(funcletizer, _expression);
285 private void ResetParameters()
287 if (this.Parameters != null)
289 bool wasLocked = ((ICollection<ObjectParameter>)this.Parameters).IsReadOnly;
292 this.Parameters.SetReadOnly(false);
294 this.Parameters.Clear();
297 this.Parameters.SetReadOnly(true);
300 _linqParameters = null;