|
//---------------------------------------------------------------------
// <copyright file="ObjectQueryProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @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;
/// <summary>
/// LINQ query provider implementation.
/// </summary>
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;
/// <summary>
/// Constructs a new provider with the given context. This constructor can be
/// called directly when initializing ObjectContext or indirectly when initializing
/// ObjectQuery.
/// </summary>
/// <param name="context">The ObjectContext of the provider.</param>
internal ObjectQueryProvider(ObjectContext context)
{
Debug.Assert(null != context, "context must be given");
_context = context;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="query"></param>
internal ObjectQueryProvider(ObjectQuery query)
: this(query.Context)
{
Debug.Assert(null != query, "query must be given");
_query = query;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="S">Element type for query result.</typeparam>
/// <param name="expression">LINQ expression forming the query.</param>
/// <returns>ObjectQuery implementing the expression logic.</returns>
IQueryable<S> IQueryProvider.CreateQuery<S>(Expression expression)
{
EntityUtil.CheckArgumentNull(expression, "expression");
if (!typeof(IQueryable<S>).IsAssignableFrom(expression.Type))
{
throw EntityUtil.Argument(System.Data.Entity.Strings.ELinq_ExpressionMustBeIQueryable, "expression");
}
ObjectQuery<S> query = CreateQuery<S>(expression);
return query;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="S">Type of returned value.</typeparam>
/// <param name="expression">Expression to evaluate.</param>
/// <returns>Single result from execution.</returns>
S IQueryProvider.Execute<S>(Expression expression)
{
EntityUtil.CheckArgumentNull(expression, "expression");
ObjectQuery<S> query = CreateQuery<S>(expression);
return ExecuteSingle<S>(query, expression);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="expression">Expression forming the query.</param>
/// <returns>ObjectQuery instance implementing the given expression.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="expression">Expression to evaluate.</param>
/// <returns>Single result from execution.</returns>
object IQueryProvider.Execute(Expression expression)
{
EntityUtil.CheckArgumentNull(expression, "expression");
ObjectQuery query = CreateQuery(expression, expression.Type);
IEnumerable<object> objQuery = Enumerable.Cast<object>(query);
return ExecuteSingle<object>(objQuery, expression);
}
/// <summary>
/// Creates a new query from an expression.
/// </summary>
/// <typeparam name="S">The element type of the query.</typeparam>
/// <param name="expression">Expression forming the query.</param>
/// <returns>A new ObjectQuery<S> instance.</returns>
private ObjectQuery<S> CreateQuery<S>(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<S>(queryState);
}
/// <summary>
/// Provides an untyped method capable of creating a strong-typed ObjectQuery
/// (based on the <paramref name="ofType"/> argument) and returning it as an
/// instance of the untyped (in a generic sense) ObjectQuery base class.
/// </summary>
/// <param name="expression">The LINQ expression that defines the new query</param>
/// <param name="ofType">The result type of the new ObjectQuery</param>
/// <returns>A new ObjectQuery<ofType>, as an instance of ObjectQuery</returns>
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
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TResult">The expected result type and the required element type of the IEnumerable collection</typeparam>
/// <param name="query">The query result set</param>
/// <param name="queryRoot">The expression that is the root of the LINQ query expression tree</param>
/// <returns>An instance of TResult if evaluation of the expression-specific singleton-producing function is successful</returns>
internal static TResult ExecuteSingle<TResult>(IEnumerable<TResult> query, Expression queryRoot)
{
return GetElementFunction<TResult>(queryRoot)(query);
}
private static Func<IEnumerable<TResult>, TResult> GetElementFunction<TResult>(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
}
}
|