Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / ELinq / ELinqQueryState.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ELinqQueryState.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.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;
23     using System.Linq;
24     using System.Linq.Expressions;
25     using System.Collections.ObjectModel;
26
27     /// <summary>
28     /// Models a Linq to Entities ObjectQuery
29     /// </summary>
30     internal class ELinqQueryState : ObjectQueryState
31     {
32         #region Private State
33
34         private readonly Expression _expression;
35         private Func<bool> _recompileRequired;
36         private ReadOnlyCollection<KeyValuePair<ObjectParameter, QueryParameterExpression>> _linqParameters;
37         private bool _useCSharpNullComparisonBehavior;
38         
39         #endregion
40
41         #region Constructors
42
43         /// <summary>
44         /// Constructs a new <see cref="ELinqQueryState"/> instance based on the specified Linq Expression
45         /// against the specified ObjectContext.
46         /// </summary>
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)
52         {
53             //
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).
57             //
58             EntityUtil.CheckArgumentNull(expression, "expression");
59             // closure bindings and initializers are explicitly allowed to be null
60
61             _expression = expression;
62             _useCSharpNullComparisonBehavior = context.ContextOptions.UseCSharpNullComparisonBehavior;
63         }
64
65         /// <summary>
66         /// Constructs a new <see cref="ELinqQueryState"/> instance based on the specified Linq Expression,
67         /// copying the state information from the specified ObjectQuery.
68         /// </summary>
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)
74         {
75             EntityUtil.CheckArgumentNull(expression, "expression");
76             _expression = expression;
77         }
78
79         #endregion
80
81         #region ObjectQueryState overrides
82
83         protected override TypeUsage GetResultType()
84         {
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;
90         }
91
92         internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMergeOption)
93         {
94             Debug.Assert(this.Span == null, "Include span specified on compiled LINQ-based ObjectQuery instead of within the expression tree?");
95
96             // If this query has already been prepared, its current execution plan may no longer be valid.
97             ObjectQueryExecutionPlan plan = this._cachedPlan;
98             if (plan != null)
99             {
100                 // Was a merge option specified in the call to Execute(MergeOption) or set via ObjectQuery.MergeOption?
101                 MergeOption? explicitMergeOption = GetMergeOption(forMergeOption, this.UserSpecifiedMergeOption);
102
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)
109                 {
110                     plan = null;
111                 }
112             }
113
114             // The plan may have been invalidated above, or this query may never have been prepared.
115             if (plan == null)
116             {
117                 // Metadata is required to generate the execution plan.
118                 this.ObjectContext.EnsureMetadata();
119
120                 // Reset internal state
121                 this._recompileRequired = null;
122                 this.ResetParameters();
123
124                 // Translate LINQ expression to a DbExpression
125                 ExpressionConverter converter = this.CreateExpressionConverter();
126                 DbExpression queryExpression = converter.Convert();
127                 
128                 // This delegate tells us when a part of the expression tree has changed requiring a recompile.
129                 this._recompileRequired = converter.RecompileRequired;
130
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);
139
140                 this._useCSharpNullComparisonBehavior = this.ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior;
141
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)
145                 {
146                     ObjectParameterCollection currentParams = this.EnsureParameters();
147                     currentParams.SetReadOnly(false);
148                     foreach (KeyValuePair<ObjectParameter, QueryParameterExpression> pair in _linqParameters)
149                     {
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);
156                     }
157                     currentParams.SetReadOnly(true);
158                 }
159
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())
164                 {
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))
169                     {
170                         cacheKey = new System.Data.Common.QueryCache.LinqQueryCacheKey(
171                                             expressionKey,
172                                             (null == this.Parameters ? 0 : this.Parameters.Count),
173                                             (null == this.Parameters ? null : this.Parameters.GetCacheKey()),
174                                             (null == converter.PropagatedSpan ? null : converter.PropagatedSpan.GetCacheKey()),
175                                             mergeOption,
176                                             this._useCSharpNullComparisonBehavior,
177                                             this.ElementType);
178
179                         cacheManager = this.ObjectContext.MetadataWorkspace.GetQueryCacheManager();
180                         ObjectQueryExecutionPlan executionPlan = null;
181                         if (cacheManager.TryCacheLookup(cacheKey, out executionPlan))
182                         {
183                             plan = executionPlan;
184                         }
185                     }
186                 }
187
188                 // If execution plan wasn't retrieved from the cache, build a new one and cache it.
189                 if (plan == null)
190                 {
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);
193
194                     // If caching is enabled then update the cache now.
195                     // Note: the logic is the same as in EntitySqlQueryState.
196                     if (cacheKey != null)
197                     {
198                         var newEntry = new QueryCacheEntry(cacheKey, plan);
199                         QueryCacheEntry foundEntry = null;
200                         if (cacheManager.TryLookupAndAdd(newEntry, out foundEntry))
201                         {
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();
205                         }
206                     }
207                 }
208
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;
212             }
213
214             // Evaluate parameter values for the query.
215             if (_linqParameters != null)
216             {
217                 foreach (KeyValuePair<ObjectParameter, QueryParameterExpression> pair in _linqParameters)
218                 {
219                     ObjectParameter parameter = pair.Key;
220                     QueryParameterExpression parameterExpression = pair.Value;
221                     if (null != parameterExpression)
222                     {
223                         parameter.Value = parameterExpression.EvaluateParameter(null);
224                     }
225                 }
226             }
227                         
228             return plan;
229         }
230
231         /// <summary>
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.
235         /// </summary>
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)
241         {
242             MethodInfo includeMethod = sourceQuery.GetType().GetMethod("Include", BindingFlags.Public | BindingFlags.Instance);
243             Debug.Assert(includeMethod != null, "Unable to find ObjectQuery.Include method?");
244
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);
248             return retState;
249         }
250
251         /// <summary>
252         /// eLINQ queries do not have command text. This method always returns <c>false</c>.
253         /// </summary>
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)
257         {
258             commandText = null;
259             return false;
260         }
261
262         /// <summary>
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.
266         /// </summary>
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)
270         {
271             expression = this.Expression;
272             return true;
273         }
274
275         #endregion
276
277         internal virtual Expression Expression { get { return _expression; } }
278                 
279         protected virtual ExpressionConverter CreateExpressionConverter()
280         {
281             Funcletizer funcletizer = Funcletizer.CreateQueryFuncletizer(this.ObjectContext);
282             return new ExpressionConverter(funcletizer, _expression);
283         }
284
285         private void ResetParameters()
286         {
287             if (this.Parameters != null)
288             {
289                 bool wasLocked = ((ICollection<ObjectParameter>)this.Parameters).IsReadOnly;
290                 if (wasLocked)
291                 {
292                     this.Parameters.SetReadOnly(false);
293                 }
294                 this.Parameters.Clear();
295                 if (wasLocked)
296                 {
297                     this.Parameters.SetReadOnly(true);
298                 }
299             }
300             _linqParameters = null;
301         }
302     }
303 }