//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupowner [....] //--------------------------------------------------------------------- namespace System.Data.Objects { using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Common.Utils; using System.Data.Metadata.Edm; using System.Data.Objects.ELinq; using System.Data.Objects.Internal; using System.Globalization; using System.Linq; /// /// ObjectQuery implements strongly-typed queries at the object-layer. /// Queries are specified using Entity-SQL strings and may be created by calling /// the Entity-SQL-based query builder methods declared by ObjectQuery. /// /// The result type of this ObjectQuery public partial class ObjectQuery : IEnumerable { #region Private Static Members // ----------------- // Static Fields // ----------------- /// /// The default query name, which is used in query-building to refer to an /// element of the ObjectQuery; e.g., in a call to ObjectQuery.Where(), a predicate of /// the form "it.Name = 'Foo'" can be specified, where "it" refers to a T. /// Note that the query name may eventually become a parameter in the command /// tree, so it must conform to the parameter name restrictions enforced by /// ObjectParameter.ValidateParameterName(string). /// private const string DefaultName = "it"; private static bool IsLinqQuery(ObjectQuery query) { return (query.QueryState is ELinqQueryState); } #endregion #region Private Instance Members // ------------------- // Private Fields // ------------------- /// /// The name of the current sequence, which defaults to "it". Used in query- /// builder methods that process an Entity-SQL command text fragment to refer to an /// instance of the return type of this query. /// private string _name = ObjectQuery.DefaultName; #endregion #region Public Constructors // ------------------- // Public Constructors // ------------------- #region ObjectQuery (string, ObjectContext) /// /// This constructor creates a new ObjectQuery instance using the specified Entity-SQL /// command as the initial query. The context specifies the connection on /// which to execute the query as well as the metadata and result cache. /// /// /// The Entity-SQL query string that initially defines the query. /// /// /// The ObjectContext containing the metadata workspace the query will /// be built against, the connection on which to execute the query, and the /// cache to store the results in. /// /// /// A new ObjectQuery instance. /// public ObjectQuery (string commandText, ObjectContext context) : this(new EntitySqlQueryState(typeof(T), commandText, false, context, null, null)) { // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type // is loaded into the workspace. If the schema types are not loaded // metadata, cache & query would be unable to reason about the type. We // either auto-load 's assembly into the ObjectItemCollection or we // auto-load the user's calling assembly and its referenced assemblies. // If the entities in the user's result spans multiple assemblies, the // user must manually call LoadFromAssembly. *GetCallingAssembly returns // the assembly of the method that invoked the currently executing method. context.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(T), System.Reflection.Assembly.GetCallingAssembly()); } #endregion #region ObjectQuery (string, ObjectContext, MergeOption) /// /// This constructor creates a new ObjectQuery instance using the specified Entity-SQL /// command as the initial query. The context specifies the connection on /// which to execute the query as well as the metadata and result cache. /// The merge option specifies how the cache should be populated/updated. /// /// /// The Entity-SQL query string that initially defines the query. /// /// /// The ObjectContext containing the metadata workspace the query will /// be built against, the connection on which to execute the query, and the /// cache to store the results in. /// /// /// The MergeOption to use when executing the query. /// /// /// A new ObjectQuery instance. /// public ObjectQuery (string commandText, ObjectContext context, MergeOption mergeOption) : this(new EntitySqlQueryState(typeof(T), commandText, false, context, null, null)) { EntityUtil.CheckArgumentMergeOption(mergeOption); this.QueryState.UserSpecifiedMergeOption = mergeOption; // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type // is loaded into the workspace. If the schema types are not loaded // metadata, cache & query would be unable to reason about the type. We // either auto-load 's assembly into the ObjectItemCollection or we // auto-load the user's calling assembly and its referenced assemblies. // If the entities in the user's result spans multiple assemblies, the // user must manually call LoadFromAssembly. *GetCallingAssembly returns // the assembly of the method that invoked the currently executing method. context.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(T), System.Reflection.Assembly.GetCallingAssembly()); } #endregion #endregion #region internal ObjectQuery (EntitySet, ObjectContext, MergeOption) constructor. /// /// This method creates a new ObjectQuery instance that represents a scan over /// the specified . This ObjectQuery carries the scan as /// and as Entity SQL. This is needed to allow case-sensitive metadata access (provided by the by default). /// The context specifies the connection on which to execute the query as well as the metadata and result cache. /// The merge option specifies how the cache should be populated/updated. /// /// /// The entity set this query scans. /// /// /// The ObjectContext containing the metadata workspace the query will /// be built against, the connection on which to execute the query, and the /// cache to store the results in. /// /// /// The MergeOption to use when executing the query. /// /// /// A new ObjectQuery instance. /// internal ObjectQuery (EntitySetBase entitySet, ObjectContext context, MergeOption mergeOption) : this(new EntitySqlQueryState(typeof(T), BuildScanEntitySetEsql(entitySet), entitySet.Scan(), false, context, null, null)) { EntityUtil.CheckArgumentMergeOption(mergeOption); this.QueryState.UserSpecifiedMergeOption = mergeOption; // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type // is loaded into the workspace. If the schema types are not loaded // metadata, cache & query would be unable to reason about the type. We // either auto-load 's assembly into the ObjectItemCollection or we // auto-load the user's calling assembly and its referenced assemblies. // If the entities in the user's result spans multiple assemblies, the // user must manually call LoadFromAssembly. *GetCallingAssembly returns // the assembly of the method that invoked the currently executing method. context.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(T), System.Reflection.Assembly.GetCallingAssembly()); } private static string BuildScanEntitySetEsql(EntitySetBase entitySet) { EntityUtil.CheckArgumentNull(entitySet, "entitySet"); return String.Format( CultureInfo.InvariantCulture, "{0}.{1}", EntityUtil.QuoteIdentifier(entitySet.EntityContainer.Name), EntityUtil.QuoteIdentifier(entitySet.Name)); } #endregion #region Public Properties /// /// The name of the query, which can be used to identify the current sequence /// by name in query-builder methods. By default, the value is "it". /// /// /// If the value specified on set is invalid. /// public string Name { get { return this._name; } set { EntityUtil.CheckArgumentNull(value, "value"); if (!ObjectParameter.ValidateParameterName(value)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_InvalidQueryName(value), "value"); } this._name = value; } } #endregion #region Query-builder Methods // --------------------- // Query-builder Methods // --------------------- /// /// This query-builder method creates a new query whose results are the /// unique results of this query. /// /// /// a new ObjectQuery instance. /// public ObjectQuery Distinct () { if (IsLinqQuery(this)) { return (ObjectQuery)Queryable.Distinct(this); } return new ObjectQuery(EntitySqlQueryBuilder.Distinct(this.QueryState)); } /// /// This query-builder method creates a new query whose results are all of /// the results of this query, except those that are also part of the other /// query specified. /// /// /// A query representing the results to exclude. /// /// /// a new ObjectQuery instance. /// /// /// If the query parameter is null. /// public ObjectQuery Except(ObjectQuery query) { EntityUtil.CheckArgumentNull(query, "query"); if (IsLinqQuery(this) || IsLinqQuery(query)) { return (ObjectQuery)Queryable.Except(this, query); } return new ObjectQuery(EntitySqlQueryBuilder.Except(this.QueryState, query.QueryState)); } /// /// This query-builder method creates a new query whose results are the results /// of this query, grouped by some criteria. /// /// /// The group keys. /// /// /// The projection list. To project the group, use the keyword "group". /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// public ObjectQuery GroupBy(string keys, string projection, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(keys, "keys"); EntityUtil.CheckArgumentNull(projection, "projection"); EntityUtil.CheckArgumentNull(parameters, "parameters"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(keys)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidGroupKeyList, "keys"); } if (StringUtil.IsNullOrEmptyOrWhiteSpace(projection)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidProjectionList, "projection"); } return new ObjectQuery(EntitySqlQueryBuilder.GroupBy(this.QueryState, this.Name, keys, projection, parameters)); } /// /// This query-builder method creates a new query whose results are those that /// are both in this query and the other query specified. /// /// /// A query representing the results to intersect with. /// /// /// a new ObjectQuery instance. /// /// /// If the query parameter is null. /// public ObjectQuery Intersect (ObjectQuery query) { EntityUtil.CheckArgumentNull(query, "query"); if (IsLinqQuery(this) || IsLinqQuery(query)) { return (ObjectQuery)Queryable.Intersect(this, query); } return new ObjectQuery(EntitySqlQueryBuilder.Intersect(this.QueryState, query.QueryState)); } /// /// This query-builder method creates a new query whose results are filtered /// to include only those of the specified type. /// /// /// a new ObjectQuery instance. /// /// /// If the type specified is invalid. /// public ObjectQuery OfType() { if (IsLinqQuery(this)) { return (ObjectQuery)Queryable.OfType(this); } // SQLPUDT 484477: Make sure TResultType is loaded. this.QueryState.ObjectContext.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(TResultType), System.Reflection.Assembly.GetCallingAssembly()); // Retrieve the O-Space type metadata for the result type specified. If no // metadata can be found for the specified type, fail. Otherwise, if the // type metadata found for TResultType is not either an EntityType or a // ComplexType, fail - OfType() is not a valid operation on scalars, // enumerations, collections, etc. Type clrOfType = typeof(TResultType); EdmType ofType = null; if (!this.QueryState.ObjectContext.MetadataWorkspace.GetItemCollection(DataSpace.OSpace).TryGetType(clrOfType.Name, clrOfType.Namespace ?? string.Empty, out ofType) || !(Helper.IsEntityType(ofType) || Helper.IsComplexType(ofType))) { throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidResultType(typeof(TResultType).FullName)); } return new ObjectQuery(EntitySqlQueryBuilder.OfType(this.QueryState, ofType, clrOfType)); } /// /// This query-builder method creates a new query whose results are the /// results of this query, ordered by some criteria. Note that any relational /// operations performed after an OrderBy have the potential to "undo" the /// ordering, so OrderBy should be considered a terminal query-building /// operation. /// /// /// The sort keys. /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// /// /// If either argument is null. /// /// /// If the sort key command text is empty. /// public ObjectQuery OrderBy (string keys, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(keys, "keys"); EntityUtil.CheckArgumentNull(parameters, "parameters"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(keys)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidSortKeyList, "keys"); } return new ObjectQuery(EntitySqlQueryBuilder.OrderBy(this.QueryState, this.Name, keys, parameters)); } /// /// This query-builder method creates a new query whose results are data /// records containing selected fields of the results of this query. /// /// /// The projection list. /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// /// /// If either argument is null. /// /// /// If the projection list command text is empty. /// public ObjectQuery Select (string projection, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(projection, "projection"); EntityUtil.CheckArgumentNull(parameters, "parameters"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(projection)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidProjectionList, "projection"); } return new ObjectQuery(EntitySqlQueryBuilder.Select(this.QueryState, this.Name, projection, parameters)); } /// /// This query-builder method creates a new query whose results are a sequence /// of values projected from the results of this query. /// /// /// The projection list. /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// /// /// If either argument is null. /// /// /// If the projection list command text is empty. /// public ObjectQuery SelectValue (string projection, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(projection, "projection"); EntityUtil.CheckArgumentNull(parameters, "parameters"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(projection)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidProjectionList, "projection"); } // SQLPUDT 484974: Make sure TResultType is loaded. this.QueryState.ObjectContext.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(TResultType), System.Reflection.Assembly.GetCallingAssembly()); return new ObjectQuery(EntitySqlQueryBuilder.SelectValue(this.QueryState, this.Name, projection, parameters, typeof(TResultType))); } /// /// This query-builder method creates a new query whose results are the /// results of this query, ordered by some criteria and with the specified /// number of results 'skipped', or paged-over. /// /// /// The sort keys. /// /// /// Specifies the number of results to skip. This must be either a constant or /// a parameter reference. /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// /// /// If any argument is null. /// /// /// If the sort key or skip count command text is empty. /// public ObjectQuery Skip (string keys, string count, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(keys, "keys"); EntityUtil.CheckArgumentNull(count, "count"); EntityUtil.CheckArgumentNull(parameters, "parameters"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(keys)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidSortKeyList, "keys"); } if (StringUtil.IsNullOrEmptyOrWhiteSpace(count)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidSkipCount, "count"); } return new ObjectQuery(EntitySqlQueryBuilder.Skip(this.QueryState, this.Name, keys, count, parameters)); } /// /// This query-builder method creates a new query whose results are the /// first 'count' results of this query. /// /// /// Specifies the number of results to return. This must be either a constant or /// a parameter reference. /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// /// /// If the top count command text is null. /// /// /// If the top count command text is empty. /// public ObjectQuery Top (string count, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(count, "count"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(count)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidTopCount, "count"); } return new ObjectQuery(EntitySqlQueryBuilder.Top(this.QueryState, this.Name, count, parameters)); } /// /// This query-builder method creates a new query whose results are all of /// the results of this query, plus all of the results of the other query, /// without duplicates (i.e., results are unique). /// /// /// A query representing the results to add. /// /// /// a new ObjectQuery instance. /// /// /// If the query parameter is null. /// public ObjectQuery Union (ObjectQuery query) { EntityUtil.CheckArgumentNull(query, "query"); if (IsLinqQuery(this) || IsLinqQuery(query)) { return (ObjectQuery)Queryable.Union(this, query); } return new ObjectQuery(EntitySqlQueryBuilder.Union(this.QueryState, query.QueryState)); } /// /// This query-builder method creates a new query whose results are all of /// the results of this query, plus all of the results of the other query, /// including any duplicates (i.e., results are not necessarily unique). /// /// /// A query representing the results to add. /// /// /// a new ObjectQuery instance. /// /// /// If the query parameter is null. /// public ObjectQuery UnionAll (ObjectQuery query) { EntityUtil.CheckArgumentNull(query, "query"); return new ObjectQuery(EntitySqlQueryBuilder.UnionAll(this.QueryState, query.QueryState)); } /// /// This query-builder method creates a new query whose results are the /// results of this query filtered by some criteria. /// /// /// The filter predicate. /// /// /// An optional set of query parameters that should be in scope when parsing. /// /// /// a new ObjectQuery instance. /// /// /// If either argument is null. /// /// /// If the filter predicate command text is empty. /// public ObjectQuery Where (string predicate, params ObjectParameter[] parameters) { EntityUtil.CheckArgumentNull(predicate, "predicate"); EntityUtil.CheckArgumentNull(parameters, "parameters"); if (StringUtil.IsNullOrEmptyOrWhiteSpace(predicate)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_QueryBuilder_InvalidFilterPredicate, "predicate"); } return new ObjectQuery(EntitySqlQueryBuilder.Where(this.QueryState, this.Name, predicate, parameters)); } #endregion } }