Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / ELinq / CompiledELinqQueryState.cs
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 //---------------------------------------------------------------------
9
10 namespace System.Data.Objects.ELinq
11 {
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;
23
24     /// <summary>
25     /// Models a compiled Linq to Entities ObjectQuery
26     /// </summary>
27     internal sealed class CompiledELinqQueryState : ELinqQueryState
28     {
29         private readonly Guid _cacheToken;
30         private readonly object[] _parameterValues;
31         private CompiledQueryCacheEntry _cacheEntry;
32
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)
43         {
44             EntityUtil.CheckArgumentNull(parameterValues, "parameterValues");
45
46             _cacheToken = cacheToken;
47             _parameterValues = parameterValues;
48
49             this.EnsureParameters();
50             this.Parameters.SetReadOnly(true);
51         }
52
53         internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMergeOption)
54         {
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");
57
58             // Metadata is required to generate the execution plan or to retrieve it from the cache.
59             this.ObjectContext.EnsureMetadata();
60
61             ObjectQueryExecutionPlan plan = null;
62             CompiledQueryCacheEntry cacheEntry = this._cacheEntry;
63             bool useCSharpNullComparisonBehavior = this.ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior;
64             if (cacheEntry != null)
65             {
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);
72
73                 // Ask for the corresponding execution plan
74                 plan = cacheEntry.GetExecutionPlan(mergeOption, useCSharpNullComparisonBehavior);
75                 if (plan == null)
76                 {
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();
81
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);
85
86                     // Update and retrieve the execution plan
87                     plan = cacheEntry.SetExecutionPlan(plan, useCSharpNullComparisonBehavior);
88                 }
89             }
90             else
91             {
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);
96
97                 if (cacheManager.TryCacheLookup(cacheKey, out cacheEntry))
98                 {
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);
104                 }
105
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)
108                 {
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);
114
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)
119                     {
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);
122
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))
126                         {
127                             cacheEntry = (CompiledQueryCacheEntry)foundEntry;
128                         }
129
130                         // We now have a cache entry, so hold onto it for future use.
131                         this._cacheEntry = cacheEntry;
132                     }
133
134                     // Recompute the effective merge option in case a cache entry was just constructed above
135                     MergeOption mergeOption = EnsureMergeOption(forMergeOption, this.UserSpecifiedMergeOption, cacheEntry.PropagatedMergeOption);
136
137                     // Ask the (retrieved or constructed) cache entry for the corresponding execution plan.
138                     plan = cacheEntry.GetExecutionPlan(mergeOption, useCSharpNullComparisonBehavior);
139                     if (plan == null)
140                     {
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);
143
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);
147                     }
148                 }
149             }
150
151             // Get parameters from the plan and set them.
152             ObjectParameterCollection currentParams = this.EnsureParameters();
153             if (plan.CompiledQueryParameters != null && plan.CompiledQueryParameters.Count > 0)
154             {
155                 currentParams.SetReadOnly(false);
156                 currentParams.Clear();
157                 foreach (KeyValuePair<ObjectParameter, QueryParameterExpression> pair in plan.CompiledQueryParameters)
158                 {
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.
164                     //
165                     ObjectParameter convertedParam = pair.Key.ShallowCopy();
166                     QueryParameterExpression parameterExpression = pair.Value;
167                     currentParams.Add(convertedParam);
168                     if (parameterExpression != null)
169                     {
170                         convertedParam.Value = parameterExpression.EvaluateParameter(_parameterValues);
171                     }
172                 }
173             }
174             currentParams.SetReadOnly(true);
175
176             Debug.Assert(plan != null, "Failed to produce an execution plan?");
177             return plan;
178         }
179
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()
185         {
186             CompiledQueryCacheEntry cacheEntry = this._cacheEntry;
187             TypeUsage resultType;
188             if (cacheEntry != null &&
189                 cacheEntry.TryGetResultType(out resultType))
190             {
191                 return resultType;
192             }
193
194             return base.GetResultType();
195         }
196
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
203         {
204             get
205             {
206                 return CreateDonateableExpressionVisitor.Replace((LambdaExpression)base.Expression, ObjectContext, _parameterValues);
207             }
208         }
209
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()
216         {
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);
221         }
222
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
228         {
229             private readonly Dictionary<ParameterExpression, object> _parameterToValueLookup;
230
231             private CreateDonateableExpressionVisitor(Dictionary<ParameterExpression, object> parameterToValueLookup)
232             {
233                 _parameterToValueLookup = parameterToValueLookup;
234             }
235
236             internal static Expression Replace(LambdaExpression query, ObjectContext objectContext, object[] parameterValues)
237             {
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);
246             }
247
248             internal override Expression VisitParameter(ParameterExpression p)
249             {
250                 object value;
251                 Expression result;
252                 if (_parameterToValueLookup.TryGetValue(p, out value))
253                 {
254                     result = Expression.Constant(value, p.Type);
255                 }
256                 else
257                 {
258                     result = base.VisitParameter(p);
259                 }
260                 return result;
261             }
262         }
263     }
264 }