1 //---------------------------------------------------------------------
2 // <copyright file="EntitySqlQueryBuilder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 //---------------------------------------------------------------------
9 namespace System.Data.Objects.Internal
11 using System.Collections.Generic;
12 using System.Data.Common;
13 using System.Data.Common.Utils;
14 using System.Data.Metadata.Edm;
15 using System.Diagnostics;
19 /// Provides Entity-SQL query building services for <see cref="EntitySqlQueryState"/>.
20 /// Knowledge of how to compose Entity-SQL fragments using query builder operators resides entirely in this class.
22 internal static class EntitySqlQueryBuilder
25 /// Helper method to extract the Entity-SQL command text from an <see cref="ObjectQueryState"/> instance if that
26 /// instance models an Entity-SQL-backed ObjectQuery, or to throw an exception indicating that query builder methods
27 /// are not supported on this query.
29 /// <param name="query">The instance from which the Entity-SQL command text should be retrieved</param>
30 /// <returns>The Entity-SQL command text, if the specified query state instance is based on Entity-SQL</returns>
31 /// <exception cref="NotSupportedException">
32 /// If the specified instance is not based on Entity-SQL command text, and so does not support Entity-SQL query builder methods
34 private static string GetCommandText(ObjectQueryState query)
36 string commandText = null;
37 if(!query.TryGetCommandText(out commandText))
39 throw EntityUtil.NotSupported(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_NotSupportedLinqSource);
46 /// Merges <see cref="ObjectParameter"/>s from a source ObjectQuery with ObjectParameters specified as an argument to a builder method.
47 /// A new <see cref="ObjectParameterCollection"/> is returned that contains copies of parameters from both <paramref name="sourceQueryParams"/> and <paramref name="builderMethodParams"/>.
49 /// <param name="context">The <see cref="ObjectContext"/> to use when constructing the new parameter collection</param>
50 /// <param name="sourceQueryParams">ObjectParameters from the ObjectQuery on which the query builder method was called</param>
51 /// <param name="builderMethodParams">ObjectParameters that were specified as an argument to the builder method</param>
52 /// <returns>A new ObjectParameterCollection containing copies of all parameters</returns>
53 private static ObjectParameterCollection MergeParameters(ObjectContext context, ObjectParameterCollection sourceQueryParams, ObjectParameter[] builderMethodParams)
55 Debug.Assert(builderMethodParams != null, "params array argument should not be null");
56 if (sourceQueryParams == null && builderMethodParams.Length == 0)
61 ObjectParameterCollection mergedParams = ObjectParameterCollection.DeepCopy(sourceQueryParams);
62 if (mergedParams == null)
64 mergedParams = new ObjectParameterCollection(context.Perspective);
67 foreach (ObjectParameter builderParam in builderMethodParams)
69 mergedParams.Add(builderParam);
76 /// Merges <see cref="ObjectParameter"/>s from two ObjectQuery arguments to SetOp builder methods (Except, Intersect, Union, UnionAll).
77 /// A new <see cref="ObjectParameterCollection"/> is returned that contains copies of parameters from both <paramref name="query1Params"/> and <paramref name="query2Params"/>.
79 /// <param name="query1Params">ObjectParameters from the first ObjectQuery argument (on which the query builder method was called)</param>
80 /// <param name="query2Params">ObjectParameters from the second ObjectQuery argument (specified as an argument to the builder method)</param>
81 /// <returns>A new ObjectParameterCollection containing copies of all parameters</returns>
82 private static ObjectParameterCollection MergeParameters(ObjectParameterCollection query1Params, ObjectParameterCollection query2Params)
84 if (query1Params == null && query2Params == null)
89 ObjectParameterCollection mergedParams;
90 ObjectParameterCollection sourceParams;
91 if (query1Params != null)
93 mergedParams = ObjectParameterCollection.DeepCopy(query1Params);
94 sourceParams = query2Params;
98 mergedParams = ObjectParameterCollection.DeepCopy(query2Params);
99 sourceParams = query1Params;
102 if (sourceParams != null)
104 foreach (ObjectParameter sourceParam in sourceParams)
106 mergedParams.Add(sourceParam.ShallowCopy());
113 private static ObjectQueryState NewBuilderQuery(ObjectQueryState sourceQuery, Type elementType, StringBuilder queryText, Span newSpan, IEnumerable<ObjectParameter> enumerableParams)
115 return NewBuilderQuery(sourceQuery, elementType, queryText, false, newSpan, enumerableParams);
118 private static ObjectQueryState NewBuilderQuery(ObjectQueryState sourceQuery, Type elementType, StringBuilder queryText, bool allowsLimit, Span newSpan, IEnumerable<ObjectParameter> enumerableParams)
120 ObjectParameterCollection queryParams = enumerableParams as ObjectParameterCollection;
121 if (queryParams == null && enumerableParams != null)
123 queryParams = new ObjectParameterCollection(sourceQuery.ObjectContext.Perspective);
124 foreach (ObjectParameter objectParam in enumerableParams)
126 queryParams.Add(objectParam);
130 EntitySqlQueryState newState = new EntitySqlQueryState(elementType, queryText.ToString(), allowsLimit, sourceQuery.ObjectContext, queryParams, newSpan);
132 sourceQuery.ApplySettingsTo(newState);
137 // Note that all query builder string constants contain embedded newlines to prevent manipulation of the
138 // query text by single line comments (--) that might appear in user-supplied portions of the string such
139 // as a filter predicate, projection list, etc.
141 #region SetOp Helpers
143 private const string _setOpEpilog =
147 private const string _setOpProlog =
151 // SetOp helper - note that this doesn't merge Spans, since Except uses the original query's Span
152 // while Intersect/Union/UnionAll use the merged Span.
153 private static ObjectQueryState BuildSetOp(ObjectQueryState leftQuery, ObjectQueryState rightQuery, Span newSpan, string setOp)
155 // Assert that the arguments aren't null (should have been verified by ObjectQuery)
156 Debug.Assert(leftQuery != null, "Left query is null?");
157 Debug.Assert(rightQuery != null, "Right query is null?");
158 Debug.Assert(leftQuery.ElementType.Equals(rightQuery.ElementType), "Incompatible element types in arguments to Except<T>/Intersect<T>/Union<T>/UnionAll<T>?");
160 // Retrieve the left and right arguments to the set operation -
161 // this will throw if either input query is not an Entity-SQL query.
162 string left = GetCommandText(leftQuery);
163 string right = GetCommandText(rightQuery);
165 // ObjectQuery arguments must be associated with the same ObjectContext instance as the implemented query
166 if (!object.ReferenceEquals(leftQuery.ObjectContext, rightQuery.ObjectContext))
168 throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidQueryArgument, "query");
171 // Create a string builder only large enough to contain the new query text
172 int queryLength = _setOpProlog.Length + left.Length + setOp.Length + right.Length + _setOpEpilog.Length;
173 StringBuilder builder = new StringBuilder(queryLength);
175 // Build the new query
176 builder.Append(_setOpProlog);
177 builder.Append(left);
178 builder.Append(setOp);
179 builder.Append(right);
180 builder.Append(_setOpEpilog);
182 // Create a new query implementation and apply the state of this implementation to it.
183 // The Span of the query argument will be merged into the new query's Span by the caller, iff the Set Op is NOT Except.
184 // See the Except, Intersect, Union and UnionAll methods in this class for examples.
185 return NewBuilderQuery(leftQuery, leftQuery.ElementType, builder, newSpan, MergeParameters(leftQuery.Parameters, rightQuery.Parameters));
189 #region Select/SelectValue Helpers
191 private const string _fromOp =
196 private const string _asOp =
200 private static ObjectQueryState BuildSelectOrSelectValue(ObjectQueryState query, string alias, string projection, ObjectParameter[] parameters, string projectOp, Type elementType)
202 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(alias), "Invalid alias");
203 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(projection), "Invalid projection");
205 string queryText = GetCommandText(query);
207 // Build the new query string - "<project op> <projection> FROM (<this query>) AS <alias>"
208 int queryLength = projectOp.Length +
215 StringBuilder builder = new StringBuilder(queryLength);
216 builder.Append(projectOp);
217 builder.Append(projection);
218 builder.Append(_fromOp);
219 builder.Append(queryText);
220 builder.Append(_asOp);
221 builder.Append(alias);
223 // Create a new EntitySqlQueryImplementation that uses the new query as its command text.
224 // Span should not be carried over from a Select or SelectValue operation.
225 return NewBuilderQuery(query, elementType, builder, null, MergeParameters(query.ObjectContext, query.Parameters, parameters));
230 #region OrderBy/Where Helper
232 private static ObjectQueryState BuildOrderByOrWhere(ObjectQueryState query, string alias, string predicateOrKeys, ObjectParameter[] parameters, string op, string skipCount, bool allowsLimit)
234 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(alias), "Invalid alias");
235 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(predicateOrKeys), "Invalid predicate/keys");
236 Debug.Assert(null == skipCount || op == _orderByOp, "Skip clause used with WHERE operator?");
238 string queryText = GetCommandText(query);
240 // Build the new query string:
241 // Either: "SELECT VALUE <alias> FROM (<this query>) AS <alias> WHERE <predicate>"
243 // Or: "SELECT VALUE <alias> FROM (<this query>) AS <alias> ORDER BY <keys> <optional: SKIP <skip>>"
244 // Depending on the value of 'op'
245 int queryLength = _selectValueOp.Length +
252 predicateOrKeys.Length;
254 if (skipCount != null)
256 queryLength += (_skipOp.Length + skipCount.Length);
259 StringBuilder builder = new StringBuilder(queryLength);
260 builder.Append(_selectValueOp);
261 builder.Append(alias);
262 builder.Append(_fromOp);
263 builder.Append(queryText);
264 builder.Append(_asOp);
265 builder.Append(alias);
267 builder.Append(predicateOrKeys);
268 if (skipCount != null)
270 builder.Append(_skipOp);
271 builder.Append(skipCount);
274 // Create a new EntitySqlQueryImplementation that uses the new query as its command text.
275 // Span is carried over, no adjustment is needed.
276 return NewBuilderQuery(query, query.ElementType, builder, allowsLimit, query.Span, MergeParameters(query.ObjectContext, query.Parameters, parameters));
284 private const string _distinctProlog =
288 private const string _distinctEpilog =
292 internal static ObjectQueryState Distinct(ObjectQueryState query)
294 // Build the new query string - "SET(<this query>)"
295 string queryText = GetCommandText(query);
296 StringBuilder builder = new StringBuilder(_distinctProlog.Length + queryText.Length + _distinctEpilog.Length);
297 builder.Append(_distinctProlog);
298 builder.Append(queryText);
299 builder.Append(_distinctEpilog);
301 // Span is carried over, no adjustment is needed
303 return NewBuilderQuery(query, query.ElementType, builder, query.Span, ObjectParameterCollection.DeepCopy(query.Parameters));
310 private const string _exceptOp =
315 internal static ObjectQueryState Except(ObjectQueryState leftQuery, ObjectQueryState rightQuery)
317 // Call the SetOp helper.
318 // Span is taken from the leftmost query.
319 return EntitySqlQueryBuilder.BuildSetOp(leftQuery, rightQuery, leftQuery.Span, _exceptOp);
326 private const string _groupByOp =
331 internal static ObjectQueryState GroupBy(ObjectQueryState query, string alias, string keys, string projection, ObjectParameter[] parameters)
333 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(alias), "Invalid alias");
334 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(alias), "Invalid keys");
335 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(projection), "Invalid projection");
337 string queryText = GetCommandText(query);
339 // Build the new query string:
340 // "SELECT <projection> FROM (<this query>) AS <alias> GROUP BY <keys>"
341 int queryLength = _selectOp.Length +
350 StringBuilder builder = new StringBuilder(queryLength);
351 builder.Append(_selectOp);
352 builder.Append(projection);
353 builder.Append(_fromOp);
354 builder.Append(queryText);
355 builder.Append(_asOp);
356 builder.Append(alias);
357 builder.Append(_groupByOp);
358 builder.Append(keys);
360 // Create a new EntitySqlQueryImplementation that uses the new query as its command text.
361 // Span should not be carried over from a GroupBy operation.
362 return NewBuilderQuery(query, typeof(DbDataRecord), builder, null, MergeParameters(query.ObjectContext, query.Parameters, parameters));
369 private const string _intersectOp =
374 internal static ObjectQueryState Intersect(ObjectQueryState leftQuery, ObjectQueryState rightQuery)
376 // Ensure the Spans of the query arguments are merged into the new query's Span.
377 Span newSpan = Span.CopyUnion(leftQuery.Span, rightQuery.Span);
378 // Call the SetOp helper.
379 return BuildSetOp(leftQuery, rightQuery, newSpan, _intersectOp);
386 private const string _ofTypeProlog =
391 private const string _ofTypeInfix =
396 private const string _ofTypeInfix2 = "].[";
398 private const string _ofTypeEpilog =
402 internal static ObjectQueryState OfType(ObjectQueryState query, EdmType newType, Type clrOfType)
404 Debug.Assert(newType != null, "OfType cannot be null");
405 Debug.Assert(Helper.IsEntityType(newType) || Helper.IsComplexType(newType), "OfType must be Entity or Complex type");
407 string queryText = GetCommandText(query);
409 // Build the new query string - "OFTYPE((<query>), [<type namespace>].[<type name>])"
410 int queryLength = _ofTypeProlog.Length +
412 _ofTypeInfix.Length +
413 newType.NamespaceName.Length +
414 (newType.NamespaceName != string.Empty ? _ofTypeInfix2.Length : 0) +
415 newType.Name.Length +
416 _ofTypeEpilog.Length;
418 StringBuilder builder = new StringBuilder(queryLength);
419 builder.Append(_ofTypeProlog);
420 builder.Append(queryText);
421 builder.Append(_ofTypeInfix);
422 if (newType.NamespaceName != string.Empty)
424 builder.Append(newType.NamespaceName);
425 builder.Append(_ofTypeInfix2);
427 builder.Append(newType.Name);
428 builder.Append(_ofTypeEpilog);
430 // Create a new EntitySqlQueryImplementation that uses the new query as its command text.
431 // Span is carried over, no adjustment is needed
432 return NewBuilderQuery(query, clrOfType, builder, query.Span, ObjectParameterCollection.DeepCopy(query.Parameters));
439 private const string _orderByOp =
444 internal static ObjectQueryState OrderBy(ObjectQueryState query, string alias, string keys, ObjectParameter[] parameters)
446 return BuildOrderByOrWhere(query, alias, keys, parameters, _orderByOp, null, true);
453 private const string _selectOp = "SELECT ";
455 internal static ObjectQueryState Select(ObjectQueryState query, string alias, string projection, ObjectParameter[] parameters)
457 return BuildSelectOrSelectValue(query, alias, projection, parameters, _selectOp, typeof(DbDataRecord));
464 private const string _selectValueOp = "SELECT VALUE ";
466 internal static ObjectQueryState SelectValue(ObjectQueryState query, string alias, string projection, ObjectParameter[] parameters, Type projectedType)
468 return BuildSelectOrSelectValue(query, alias, projection, parameters, _selectValueOp, projectedType);
475 private const string _skipOp =
480 internal static ObjectQueryState Skip(ObjectQueryState query, string alias, string keys, string count, ObjectParameter[] parameters)
482 Debug.Assert(!StringUtil.IsNullOrEmptyOrWhiteSpace(count), "Invalid skip count");
483 return BuildOrderByOrWhere(query, alias, keys, parameters, _orderByOp, count, true);
490 private const string _limitOp =
494 private const string _topOp =
498 private const string _topInfix =
502 internal static ObjectQueryState Top(ObjectQueryState query, string alias, string count, ObjectParameter[] parameters)
504 int queryLength = count.Length;
505 string queryText = GetCommandText(query);
506 bool limitAllowed = ((EntitySqlQueryState)query).AllowsLimitSubclause;
510 // Build the new query string:
511 // <this query> LIMIT <count>
512 queryLength += (queryText.Length +
514 // + count.Length is added above
519 // Build the new query string:
520 // "SELECT VALUE TOP(<count>) <alias> FROM (<this query>) AS <alias>"
521 queryLength += (_topOp.Length +
522 // count.Length + is added above
531 StringBuilder builder = new StringBuilder(queryLength);
534 builder.Append(queryText);
535 builder.Append(_limitOp);
536 builder.Append(count);
540 builder.Append(_topOp);
541 builder.Append(count);
542 builder.Append(_topInfix);
543 builder.Append(alias);
544 builder.Append(_fromOp);
545 builder.Append(queryText);
546 builder.Append(_asOp);
547 builder.Append(alias);
550 // Create a new EntitySqlQueryImplementation that uses the new query as its command text.
551 // Span is carried over, no adjustment is needed.
552 return NewBuilderQuery(query, query.ElementType, builder, query.Span, MergeParameters(query.ObjectContext, query.Parameters, parameters));
559 private const string _unionOp =
564 internal static ObjectQueryState Union(ObjectQueryState leftQuery, ObjectQueryState rightQuery)
566 // Ensure the Spans of the query arguments are merged into the new query's Span.
567 Span newSpan = Span.CopyUnion(leftQuery.Span, rightQuery.Span);
568 // Call the SetOp helper.
569 return BuildSetOp(leftQuery, rightQuery, newSpan, _unionOp);
576 private const string _unionAllOp =
581 internal static ObjectQueryState UnionAll(ObjectQueryState leftQuery, ObjectQueryState rightQuery)
583 // Ensure the Spans of the query arguments are merged into the new query's Span.
584 Span newSpan = Span.CopyUnion(leftQuery.Span, rightQuery.Span);
585 // Call the SetOp helper.
586 return BuildSetOp(leftQuery, rightQuery, newSpan, _unionAllOp);
593 private const string _whereOp =
598 internal static ObjectQueryState Where(ObjectQueryState query, string alias, string predicate, ObjectParameter[] parameters)
600 return BuildOrderByOrWhere(query, alias, predicate, parameters, _whereOp, null, false);