//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner Microsoft // @backupOwner Microsoft //--------------------------------------------------------------------- namespace System.Data.Objects.ELinq { using System; using System.Collections.Generic; using System.Data.Objects.Internal; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; /// /// LINQ query provider implementation. /// internal sealed class ObjectQueryProvider : IQueryProvider { // Although ObjectQuery contains a reference to ObjectContext, it is possible // that IQueryProvider methods be directly invoked from the ObjectContext. // This requires having a separate field to store ObjectContext reference. private readonly ObjectContext _context; private readonly ObjectQuery _query; /// /// Constructs a new provider with the given context. This constructor can be /// called directly when initializing ObjectContext or indirectly when initializing /// ObjectQuery. /// /// The ObjectContext of the provider. internal ObjectQueryProvider(ObjectContext context) { Debug.Assert(null != context, "context must be given"); _context = context; } /// /// Constructs a new provider with the given ObjectQuery. This ObjectQuery instance /// is used to transfer state information to the new ObjectQuery instance created using /// the private CreateQuery method overloads. /// /// internal ObjectQueryProvider(ObjectQuery query) : this(query.Context) { Debug.Assert(null != query, "query must be given"); _query = query; } /// /// Creates a new query instance using the given LINQ expresion. /// The current query is used to produce the context for the new query, but none of its logic /// is used. /// /// Element type for query result. /// LINQ expression forming the query. /// ObjectQuery implementing the expression logic. IQueryable IQueryProvider.CreateQuery(Expression expression) { EntityUtil.CheckArgumentNull(expression, "expression"); if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ELinq_ExpressionMustBeIQueryable, "expression"); } ObjectQuery query = CreateQuery(expression); return query; } /// /// Executes the given LINQ expression returning a single value, or null if the query yields /// no results. If the return type is unexpected, raises a cast exception. /// The current query is used to produce the context for the new query, but none of its logic /// is used. /// /// Type of returned value. /// Expression to evaluate. /// Single result from execution. S IQueryProvider.Execute(Expression expression) { EntityUtil.CheckArgumentNull(expression, "expression"); ObjectQuery query = CreateQuery(expression); return ExecuteSingle(query, expression); } /// /// Creates a new query instance using the given LINQ expresion. /// The current query is used to produce the context for the new query, but none of its logic /// is used. /// /// Expression forming the query. /// ObjectQuery instance implementing the given expression. IQueryable IQueryProvider.CreateQuery(Expression expression) { EntityUtil.CheckArgumentNull(expression, "expression"); if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { throw EntityUtil.Argument(System.Data.Entity.Strings.ELinq_ExpressionMustBeIQueryable, "expression"); } // Determine the type of the query instance by binding generic parameter in Query<>.Queryable // (based on element type of expression) Type elementType = TypeSystem.GetElementType(expression.Type); ObjectQuery query = CreateQuery(expression, elementType); return query; } /// /// Executes the given LINQ expression returning a single value, or null if the query yields /// no results. /// The current query is used to produce the context for the new query, but none of its logic /// is used. /// /// Expression to evaluate. /// Single result from execution. object IQueryProvider.Execute(Expression expression) { EntityUtil.CheckArgumentNull(expression, "expression"); ObjectQuery query = CreateQuery(expression, expression.Type); IEnumerable objQuery = Enumerable.Cast(query); return ExecuteSingle(objQuery, expression); } /// /// Creates a new query from an expression. /// /// The element type of the query. /// Expression forming the query. /// A new ObjectQuery<S> instance. private ObjectQuery CreateQuery(Expression expression) { ObjectQueryState queryState; if (_query == null) { queryState = new ELinqQueryState(typeof(S), _context, expression); } else { queryState = new ELinqQueryState(typeof(S), _query, expression); } return new ObjectQuery(queryState); } /// /// Provides an untyped method capable of creating a strong-typed ObjectQuery /// (based on the argument) and returning it as an /// instance of the untyped (in a generic sense) ObjectQuery base class. /// /// The LINQ expression that defines the new query /// The result type of the new ObjectQuery /// A new ObjectQuery<ofType>, as an instance of ObjectQuery private ObjectQuery CreateQuery(Expression expression, Type ofType) { ObjectQueryState queryState; if (_query == null) { queryState = new ELinqQueryState(ofType, _context, expression); } else { queryState = new ELinqQueryState(ofType, _query, expression); } return queryState.CreateQuery(); } #region Internal Utility API /// /// Uses an expression-specific 'materialization' function to produce /// a singleton result from an IEnumerable query result. The function /// used depends on the semantics required by the expression that is /// the root of the query. First,FirstOrDefault and SingleOrDefault are /// currently handled as special cases, and the default behavior is to /// use the Enumerable.Single materialization pattern. /// /// The expected result type and the required element type of the IEnumerable collection /// The query result set /// The expression that is the root of the LINQ query expression tree /// An instance of TResult if evaluation of the expression-specific singleton-producing function is successful internal static TResult ExecuteSingle(IEnumerable query, Expression queryRoot) { return GetElementFunction(queryRoot)(query); } private static Func, TResult> GetElementFunction(Expression queryRoot) { SequenceMethod seqMethod; if (ReflectionUtil.TryIdentifySequenceMethod(queryRoot, true /*unwrapLambdas*/, out seqMethod)) { switch (seqMethod) { case SequenceMethod.First: case SequenceMethod.FirstPredicate: return (sequence) => { return Enumerable.First(sequence); }; case SequenceMethod.FirstOrDefault: case SequenceMethod.FirstOrDefaultPredicate: return (sequence) => { return Enumerable.FirstOrDefault(sequence); }; case SequenceMethod.SingleOrDefault: case SequenceMethod.SingleOrDefaultPredicate: return (sequence) => { return Enumerable.SingleOrDefault(sequence); }; } } return (sequence) => { return Enumerable.Single(sequence); }; } #endregion } }