Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ELinqQueryState.cs « ELinq « Objects « Data « System « System.Data.Entity « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6a68bcb8bb7a600e4049ce9cd9a3e2311dc75481 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//---------------------------------------------------------------------
// <copyright file="ELinqQueryState.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------

namespace System.Data.Objects.ELinq
{
    using System;
    using System.Collections.Generic;
    using System.Data.Common.CommandTrees;
    using System.Data.Common.CommandTrees.Internal;
    using System.Data.Common.QueryCache;
    using System.Data.Metadata.Edm;
    using System.Data.Objects;
    using System.Data.Objects.ELinq;
    using System.Data.Objects.Internal;
    using System.Diagnostics;
    using System.Reflection;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Collections.ObjectModel;

    /// <summary>
    /// Models a Linq to Entities ObjectQuery
    /// </summary>
    internal class ELinqQueryState : ObjectQueryState
    {
        #region Private State

        private readonly Expression _expression;
        private Func<bool> _recompileRequired;
        private ReadOnlyCollection<KeyValuePair<ObjectParameter, QueryParameterExpression>> _linqParameters;
        private bool _useCSharpNullComparisonBehavior;
        
        #endregion

        #region Constructors

        /// <summary>
        /// Constructs a new <see cref="ELinqQueryState"/> instance based on the specified Linq Expression
        /// against the specified ObjectContext.
        /// </summary>
        /// <param name="elementType">The element type of the implemented ObjectQuery, as a CLR type.</param>
        /// <param name="context">The ObjectContext with which the implemented ObjectQuery is associated.</param>
        /// <param name="expression">The Linq Expression that defines this query.</param>
        internal ELinqQueryState(Type elementType, ObjectContext context, Expression expression)
            : base(elementType, context, null, null)
        {
            //
            // Initialize the LINQ expression, which is passed in via
            // public APIs on ObjectQuery and must be checked here
            // (the base class performs similar checks on the ObjectContext and MergeOption arguments).
            //
            EntityUtil.CheckArgumentNull(expression, "expression");
            // closure bindings and initializers are explicitly allowed to be null

            _expression = expression;
            _useCSharpNullComparisonBehavior = context.ContextOptions.UseCSharpNullComparisonBehavior;
        }

        /// <summary>
        /// Constructs a new <see cref="ELinqQueryState"/> instance based on the specified Linq Expression,
        /// copying the state information from the specified ObjectQuery.
        /// </summary>
        /// <param name="elementType">The element type of the implemented ObjectQuery, as a CLR type.</param>
        /// <param name="query">The ObjectQuery from which the state information should be copied.</param>
        /// <param name="expression">The Linq Expression that defines this query.</param>
        internal ELinqQueryState(Type elementType, ObjectQuery query, Expression expression)
            : base(elementType, query)
        {
            EntityUtil.CheckArgumentNull(expression, "expression");
            _expression = expression;
        }

        #endregion

        #region ObjectQueryState overrides

        protected override TypeUsage GetResultType()
        {
            // Since this method is only called once, on demand, a full conversion pass
            // is performed to produce the DbExpression and return its result type. 
            // This does not affect any cached execution plan or closure bindings that may be present.
            ExpressionConverter converter = this.CreateExpressionConverter();
            return converter.Convert().ResultType;
        }

        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;
        }

        /// <summary>
        /// Returns a new ObjectQueryState instance with the specified navigation property path specified as an Include span.
        /// For eLINQ queries the Include operation is modelled as a method call expression applied to the source ObectQuery,
        /// so the <see cref="Span"/> property is always <c>null</c> on the returned instance.
        /// </summary>
        /// <typeparam name="TElementType">The element type of the resulting query</typeparam>
        /// <param name="sourceQuery">The ObjectQuery on which Include was called; required to build the new method call expression</param>
        /// <param name="includePath">The new Include path</param>
        /// <returns>A new ObjectQueryState instance that incorporates the Include path, in this case a new method call expression</returns>
        internal override ObjectQueryState Include<TElementType>(ObjectQuery<TElementType> sourceQuery, string includePath)
        {
            MethodInfo includeMethod = sourceQuery.GetType().GetMethod("Include", BindingFlags.Public | BindingFlags.Instance);
            Debug.Assert(includeMethod != null, "Unable to find ObjectQuery.Include method?");

            Expression includeCall = Expression.Call(Expression.Constant(sourceQuery), includeMethod, new Expression[] { Expression.Constant(includePath, typeof(string)) });
            ObjectQueryState retState = new ELinqQueryState(this.ElementType, this.ObjectContext, includeCall);
            this.ApplySettingsTo(retState);
            return retState;
        }

        /// <summary>
        /// eLINQ queries do not have command text. This method always returns <c>false</c>.
        /// </summary>
        /// <param name="commandText">Always set to <c>null</c></param>
        /// <returns>Always returns <c>false</c></returns>
        internal override bool TryGetCommandText(out string commandText)
        {
            commandText = null;
            return false;
        }

        /// <summary>
        /// Gets the LINQ Expression that defines this query for external (of ObjectQueryState) use.
        /// Note that the <see cref="Expression"/> property is used, which is overridden by compiled eLINQ
        /// queries to produce an Expression tree where parameter references have been replaced with constants.
        /// </summary>
        /// <param name="expression">The LINQ expression that describes this query</param>
        /// <returns>Always returns <c>true</c></returns>
        internal override bool TryGetExpression(out System.Linq.Expressions.Expression expression)
        {
            expression = this.Expression;
            return true;
        }

        #endregion

        internal virtual Expression Expression { get { return _expression; } }
                
        protected virtual ExpressionConverter CreateExpressionConverter()
        {
            Funcletizer funcletizer = Funcletizer.CreateQueryFuncletizer(this.ObjectContext);
            return new ExpressionConverter(funcletizer, _expression);
        }

        private void ResetParameters()
        {
            if (this.Parameters != null)
            {
                bool wasLocked = ((ICollection<ObjectParameter>)this.Parameters).IsReadOnly;
                if (wasLocked)
                {
                    this.Parameters.SetReadOnly(false);
                }
                this.Parameters.Clear();
                if (wasLocked)
                {
                    this.Parameters.SetReadOnly(true);
                }
            }
            _linqParameters = null;
        }
    }
}