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