File: EntitySqlQueryState.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (270 lines) | stat: -rw-r--r-- 12,704 bytes parent folder | download | duplicates (8)
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
//---------------------------------------------------------------------
// <copyright file="EntitySqlQueryState.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner  Microsoft
//---------------------------------------------------------------------

namespace System.Data.Objects
{
    using System.Collections.Generic;
    using System.Data.Common.CommandTrees;
    using System.Data.Common.CommandTrees.ExpressionBuilder;
    using System.Data.Common.EntitySql;
    using System.Data.Common.QueryCache;
    using System.Data.Metadata.Edm;
    using System.Data.Objects.Internal;
    using System.Diagnostics;

    /// <summary>
    /// ObjectQueryState based on Entity-SQL query text.
    /// </summary>
    internal sealed class EntitySqlQueryState : ObjectQueryState
    {
        /// <summary>
        /// The Entity-SQL text that defines the query.
        /// </summary>
        /// <remarks>
        /// It is important that this field is readonly for consistency reasons wrt <see cref="_queryExpression"/>.
        /// If this field becomes read-write, then write should be allowed only when <see cref="_queryExpression"/> is null, 
        /// or there should be a mechanism keeping both fields consistent.
        /// </remarks>
        private readonly string _queryText;

        /// <summary>
        /// Optional <see cref="DbExpression"/> that defines the query. Must be semantically equal to the <see cref="_queryText"/>.
        /// </summary>
        /// <remarks>
        /// It is important that this field is readonly for consistency reasons wrt <see cref="_queryText"/>.
        /// If this field becomes read-write, then there should be a mechanism keeping both fields consistent.
        /// </remarks>
        private readonly DbExpression _queryExpression;

        /// <summary>
        ///     Can a Limit subclause be appended to the text of this query?
        /// </summary>
        private readonly bool _allowsLimit;

        /// <summary>
        /// Initializes a new query EntitySqlQueryState instance.
        /// </summary>
        /// <param name="context">
        ///     The ObjectContext containing the metadata workspace the query was
        ///     built against, the connection on which to execute the query, and the
        ///     cache to store the results in. Must not be null.
        /// </param>
        /// <param name="commandText">
        ///     The Entity-SQL text of the query
        /// </param>
        /// <param name="mergeOption">
        ///     The merge option to use when retrieving results if an explicit merge option is not specified
        /// </param>
        internal EntitySqlQueryState(Type elementType, string commandText, bool allowsLimit, ObjectContext context, ObjectParameterCollection parameters, Span span)
            : this(elementType, commandText, /*expression*/ null, allowsLimit, context, parameters, span)
        { }
        
        /// <summary>
        /// Initializes a new query EntitySqlQueryState instance.
        /// </summary>
        /// <param name="context">
        ///     The ObjectContext containing the metadata workspace the query was
        ///     built against, the connection on which to execute the query, and the
        ///     cache to store the results in. Must not be null.
        /// </param>
        /// <param name="commandText">
        ///     The Entity-SQL text of the query
        /// </param>
        /// <param name="expression">
        ///     Optional <see cref="DbExpression"/> that defines the query. Must be semantically equal to the <paramref name="commandText"/>.
        /// </param>
        /// <param name="mergeOption">
        ///     The merge option to use when retrieving results if an explicit merge option is not specified
        /// </param>
        internal EntitySqlQueryState(Type elementType, string commandText, DbExpression expression, bool allowsLimit, ObjectContext context, ObjectParameterCollection parameters, Span span)
            : base(elementType, context, parameters, span)
        {
            EntityUtil.CheckArgumentNull(commandText, "commandText");
            if (string.IsNullOrEmpty(commandText))
            {
                throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_InvalidEmptyQuery, "commandText");
            }

            _queryText = commandText;
            _queryExpression = expression;
            _allowsLimit = allowsLimit;
        }

        /// <summary>
        ///     Determines whether or not the current query is a 'Skip' or 'Sort' operation
        ///     and so would allow a 'Limit' clause to be appended to the current query text.
        /// </summary>
        /// <returns>
        ///     <c>True</c> if the current query is a Skip or Sort expression, or a
        ///     Project expression with a Skip or Sort expression input.
        /// </returns>
        internal bool AllowsLimitSubclause { get { return _allowsLimit; } }

        /// <summary>
        /// Always returns the Entity-SQL text of the implemented ObjectQuery.
        /// </summary>
        /// <param name="commandText">Always set to the Entity-SQL text of this ObjectQuery.</param>
        /// <returns>Always returns <c>true</c>.</returns>
        internal override bool TryGetCommandText(out string commandText)
        {
            commandText = this._queryText;
            return true;
        }

        internal override bool TryGetExpression(out System.Linq.Expressions.Expression expression)
        {
            expression = null;
            return false;
        }

        protected override TypeUsage GetResultType()
        {
            DbExpression query = this.Parse();
            return query.ResultType;
        }

        internal override ObjectQueryState Include<TElementType>(ObjectQuery<TElementType> sourceQuery, string includePath)
        {
            ObjectQueryState retState = new EntitySqlQueryState(this.ElementType, _queryText, _queryExpression, _allowsLimit, this.ObjectContext, ObjectParameterCollection.DeepCopy(this.Parameters), Span.IncludeIn(this.Span, includePath));
            this.ApplySettingsTo(retState);
            return retState;
        }

        internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMergeOption)
        {
            // Metadata is required to generate the execution plan or to retrieve it from the cache.
            this.ObjectContext.EnsureMetadata();

            // Determine the required merge option, with the following precedence:
            // 1. The merge option specified to Execute(MergeOption) as forMergeOption.
            // 2. The merge option set via ObjectQuery.MergeOption.
            // 3. The global default merge option.
            MergeOption mergeOption = EnsureMergeOption(forMergeOption, this.UserSpecifiedMergeOption);

            // If a cached plan is present, then it can be reused if it has the required merge option
            // (since span and parameters cannot change between executions). However, if the cached
            // plan does not have the required merge option we proceed as if it were not present.
            ObjectQueryExecutionPlan plan = this._cachedPlan;
            if (plan != null)
            {
                if (plan.MergeOption == mergeOption)
                {
                    return plan;
                }
                else
                {
                    plan = null;
                }
            }

            // There is no cached plan (or it was cleared), so the execution plan must be retrieved from
            // the global query cache (if plan caching is enabled) or rebuilt for the required merge option.
            QueryCacheManager cacheManager = null;
            EntitySqlQueryCacheKey cacheKey = null;
            if (this.PlanCachingEnabled)
            {
                // 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.
                cacheKey = new EntitySqlQueryCacheKey(
                                   this.ObjectContext.DefaultContainerName,
                                   _queryText,
                                   (null == this.Parameters ? 0 : this.Parameters.Count),
                                   (null == this.Parameters ? null : this.Parameters.GetCacheKey()),
                                   (null == this.Span ? null : this.Span.GetCacheKey()),
                                   mergeOption,
                                   this.ElementType);

                cacheManager = this.ObjectContext.MetadataWorkspace.GetQueryCacheManager();
                ObjectQueryExecutionPlan executionPlan = null;
                if (cacheManager.TryCacheLookup(cacheKey, out executionPlan))
                {
                    plan = executionPlan;
                }
            }

            if (plan == null)
            {
                // Either caching is not enabled or the execution plan was not found in the cache
                DbExpression queryExpression = this.Parse();
                Debug.Assert(queryExpression != null, "EntitySqlQueryState.Parse returned null expression?");
                DbQueryCommandTree tree = DbQueryCommandTree.FromValidExpression(this.ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression);
                plan = ObjectQueryExecutionPlan.Prepare(this.ObjectContext, tree, this.ElementType, mergeOption, this.Span, null, DbExpressionBuilder.AliasGenerator);

                // If caching is enabled then update the cache now.
                // Note: the logic is the same as in ELinqQueryState.
                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();
                    }
                }
            }

            if (this.Parameters != null)
            {
                this.Parameters.SetReadOnly(true);
            }

            // Update the cached plan with the newly retrieved/prepared plan
            this._cachedPlan = plan;

            // Return the execution plan
            return plan;
        }

        internal DbExpression Parse()
        {
            if (_queryExpression != null)
            {
                return _queryExpression;
            }

            List<DbParameterReferenceExpression> parameters = null;
            if (this.Parameters != null)
            {
                parameters = new List<DbParameterReferenceExpression>(this.Parameters.Count);
                foreach (ObjectParameter parameter in this.Parameters)
                {
                    TypeUsage typeUsage = parameter.TypeUsage;
                    if (null == typeUsage)
                    {
                        // Since ObjectParameters do not allow users to specify 'facets', make 
                        // sure that the parameter TypeUsage is not populated with the provider
                        // default facet values.
                        this.ObjectContext.Perspective.TryGetTypeByName(
                                        parameter.MappableType.FullName,
                                        false /* bIgnoreCase */,
                                        out typeUsage);
                    }

                    Debug.Assert(typeUsage != null, "typeUsage != null");
                    
                    parameters.Add(typeUsage.Parameter(parameter.Name));
                }
            }

            DbLambda lambda =
                CqlQuery.CompileQueryCommandLambda(
                    _queryText,                     // Command Text
                    this.ObjectContext.Perspective, // Perspective
                    null,                           // Parser options - null indicates 'use default'
                    parameters,                     // Parameters
                    null                            // Variables
                );

            Debug.Assert(lambda.Variables == null || lambda.Variables.Count == 0, "lambda.Variables must be empty");

            return lambda.Body;
        }
    }
}