Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / ELinq / CompiledELinqQueryState.cs
blobd072e7c3b080ef28c04abf45523dc31771113d4a
1 //---------------------------------------------------------------------
2 // <copyright file="CompiledELinqQueryState.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.Objects.ELinq
12 using System;
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;
21 using System.Linq;
22 using System.Linq.Expressions;
24 /// <summary>
25 /// Models a compiled Linq to Entities ObjectQuery
26 /// </summary>
27 internal sealed class CompiledELinqQueryState : ELinqQueryState
29 private readonly Guid _cacheToken;
30 private readonly object[] _parameterValues;
31 private CompiledQueryCacheEntry _cacheEntry;
33 /// <summary>
34 /// Factory method to create a new compiled query state instance
35 /// </summary>
36 /// <param name="elementType">The element type of the new instance (the 'T' of the ObjectQuery&lt;T&gt; 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);
75 if (plan == null)
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);
90 else
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.
107 if (plan == null)
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);
139 if (plan == null)
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?");
177 return plan;
180 /// <summary>
181 /// Overrides GetResultType and attempts to first retrieve the result type from the cache entry.
182 /// </summary>
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))
191 return resultType;
194 return base.GetResultType();
197 /// <summary>
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.
201 /// </summary>
202 internal override Expression Expression
206 return CreateDonateableExpressionVisitor.Replace((LambdaExpression)base.Expression, ObjectContext, _parameterValues);
210 /// <summary>
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.
213 /// </summary>
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);
223 /// <summary>
224 /// Replaces ParameterExpresion with ConstantExpression
225 /// to make the expression usable as a donor expression
226 /// </summary>
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
239 .Parameters
240 .Skip(1)
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)
250 object value;
251 Expression result;
252 if (_parameterToValueLookup.TryGetValue(p, out value))
254 result = Expression.Constant(value, p.Type);
256 else
258 result = base.VisitParameter(p);
260 return result;