e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
750 lines
31 KiB
C#
750 lines
31 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="Funcletizer.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
// @backupOwner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
namespace System.Data.Objects.ELinq
|
|
{
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Data.Common.CommandTrees;
|
|
using System.Data.Common.CommandTrees.ExpressionBuilder;
|
|
using System.Data.Entity;
|
|
using System.Data.Metadata.Edm;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
|
|
/// <summary>
|
|
/// Determines which leaves of a LINQ expression tree should be evaluated locally before
|
|
/// sending a query to the store. These sub-expressions may map to query parameters (e.g. local variables),
|
|
/// to constants (e.g. literals 'new DateTime(2008, 1, 1)') or query sub-expression
|
|
/// (e.g. 'context.Products'). Parameter expressions are replaced with QueryParameterExpression
|
|
/// nodes. All other elements are swapped in place with either expanded expressions (for sub-queries)
|
|
/// or constants. Where the expression includes mutable state that may influence the translation
|
|
/// to a query, a Func(Of Boolean) delegate is returned indicating when a recompilation is necessary.
|
|
/// </summary>
|
|
internal sealed class Funcletizer
|
|
{
|
|
// Compiled query information
|
|
private readonly ParameterExpression _rootContextParameter;
|
|
private readonly ObjectContext _rootContext;
|
|
private readonly ConstantExpression _rootContextExpression;
|
|
private readonly ReadOnlyCollection<ParameterExpression> _compiledQueryParameters;
|
|
private readonly Mode _mode;
|
|
private readonly HashSet<Expression> _linqExpressionStack = new HashSet<Expression>();
|
|
|
|
// Object parameters
|
|
private static readonly string s_parameterPrefix = "p__linq__";
|
|
private long _parameterNumber;
|
|
|
|
private Funcletizer(
|
|
Mode mode,
|
|
ObjectContext rootContext,
|
|
ParameterExpression rootContextParameter,
|
|
ReadOnlyCollection<ParameterExpression> compiledQueryParameters)
|
|
{
|
|
_mode = mode;
|
|
_rootContext = rootContext;
|
|
_rootContextParameter = rootContextParameter;
|
|
_compiledQueryParameters = compiledQueryParameters;
|
|
if (null != _rootContextParameter && null != _rootContext)
|
|
{
|
|
_rootContextExpression = Expression.Constant(_rootContext);
|
|
}
|
|
}
|
|
|
|
internal static Funcletizer CreateCompiledQueryEvaluationFuncletizer(
|
|
ObjectContext rootContext,
|
|
ParameterExpression rootContextParameter,
|
|
ReadOnlyCollection<ParameterExpression> compiledQueryParameters)
|
|
{
|
|
EntityUtil.CheckArgumentNull(rootContext, "rootContext");
|
|
EntityUtil.CheckArgumentNull(rootContextParameter, "rootContextParameter");
|
|
EntityUtil.CheckArgumentNull(compiledQueryParameters, "compiledQueryParameters");
|
|
|
|
return new Funcletizer(Mode.CompiledQueryEvaluation, rootContext, rootContextParameter, compiledQueryParameters);
|
|
}
|
|
|
|
internal static Funcletizer CreateCompiledQueryLockdownFuncletizer()
|
|
{
|
|
return new Funcletizer(Mode.CompiledQueryLockdown, null, null, null);
|
|
}
|
|
|
|
internal static Funcletizer CreateQueryFuncletizer(ObjectContext rootContext)
|
|
{
|
|
EntityUtil.CheckArgumentNull(rootContext, "rootContext");
|
|
|
|
return new Funcletizer(Mode.ConventionalQuery, rootContext, null, null);
|
|
}
|
|
|
|
internal ObjectContext RootContext
|
|
{
|
|
get { return _rootContext; }
|
|
}
|
|
|
|
internal ParameterExpression RootContextParameter
|
|
{
|
|
get { return _rootContextParameter; }
|
|
}
|
|
|
|
internal ConstantExpression RootContextExpression
|
|
{
|
|
get { return _rootContextExpression; }
|
|
}
|
|
|
|
internal bool IsCompiledQuery
|
|
{
|
|
get { return _mode == Mode.CompiledQueryEvaluation || _mode == Mode.CompiledQueryLockdown; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs funcletization on the given expression. Also returns a delegates that can be used
|
|
/// to determine if the entire tree needs to be recompiled.
|
|
/// </summary>
|
|
internal Expression Funcletize(Expression expression, out Func<bool> recompileRequired)
|
|
{
|
|
EntityUtil.CheckArgumentNull(expression, "expression");
|
|
|
|
// Find all candidates for funcletization. Some sub-expressions are reduced to constants,
|
|
// others are reduced to variables. The rules vary based on the _mode.
|
|
Func<Expression, bool> isClientConstant;
|
|
Func<Expression, bool> isClientVariable;
|
|
|
|
expression = ReplaceRootContextParameter(expression);
|
|
|
|
if (_mode == Mode.CompiledQueryEvaluation)
|
|
{
|
|
// We lock down closure expressions for compiled queries, so everything is either
|
|
// a constant or a query parameter produced from the explicit parameters to the
|
|
// compiled query delegate.
|
|
isClientConstant = Nominate(expression, this.IsClosureExpression);
|
|
isClientVariable = Nominate(expression, this.IsCompiledQueryParameterVariable);
|
|
}
|
|
else if (_mode == Mode.CompiledQueryLockdown)
|
|
{
|
|
// When locking down a compiled query, we can evaluate all closure expressions.
|
|
isClientConstant = Nominate(expression, this.IsClosureExpression);
|
|
isClientVariable = (exp) => false;
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(_mode == Mode.ConventionalQuery, "No other options...");
|
|
|
|
// There are no variable parameters outside of compiled queries, so everything is
|
|
// either a constant or a closure expression.
|
|
isClientConstant = Nominate(expression, this.IsImmutable);
|
|
isClientVariable = Nominate(expression, this.IsClosureExpression);
|
|
}
|
|
|
|
// Now rewrite given nomination functions
|
|
FuncletizingVisitor visitor = new FuncletizingVisitor(this, isClientConstant, isClientVariable);
|
|
Expression result = visitor.Visit(expression);
|
|
recompileRequired = visitor.GetRecompileRequiredFunction();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces context parameter (e.g. 'ctx' in CompiledQuery.Compile(ctx => ctx.Products)) with constant
|
|
/// containing the object context.
|
|
/// </summary>
|
|
private Expression ReplaceRootContextParameter(Expression expression)
|
|
{
|
|
if (null != _rootContextExpression)
|
|
{
|
|
return EntityExpressionVisitor.Visit(
|
|
expression, (exp, baseVisit) =>
|
|
exp == _rootContextParameter ? _rootContextExpression : baseVisit(exp));
|
|
}
|
|
else
|
|
{
|
|
return expression;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a function indicating whether the given expression and all of its children satisfy the
|
|
/// 'localCriterion'.
|
|
/// </summary>
|
|
private static Func<Expression, bool> Nominate(Expression expression, Func<Expression, bool> localCriterion)
|
|
{
|
|
EntityUtil.CheckArgumentNull(localCriterion, "localCriterion");
|
|
HashSet<Expression> candidates = new HashSet<Expression>();
|
|
bool cannotBeNominated = false;
|
|
Func<Expression, Func<Expression, Expression>, Expression> visit = (exp, baseVisit) =>
|
|
{
|
|
if (exp != null)
|
|
{
|
|
bool saveCannotBeNominated = cannotBeNominated;
|
|
cannotBeNominated = false;
|
|
baseVisit(exp);
|
|
if (!cannotBeNominated)
|
|
{
|
|
// everyone below me can be nominated, so
|
|
// see if this one can be also
|
|
if (localCriterion(exp))
|
|
{
|
|
candidates.Add(exp);
|
|
}
|
|
else
|
|
{
|
|
cannotBeNominated = true;
|
|
}
|
|
}
|
|
cannotBeNominated |= saveCannotBeNominated;
|
|
}
|
|
return exp;
|
|
};
|
|
EntityExpressionVisitor.Visit(expression, visit);
|
|
return candidates.Contains;
|
|
}
|
|
|
|
private enum Mode
|
|
{
|
|
CompiledQueryLockdown,
|
|
CompiledQueryEvaluation,
|
|
ConventionalQuery,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the node may be evaluated locally and whether
|
|
/// it is a constant. Assumes that all children are also client expressions.
|
|
/// </summary>
|
|
private bool IsImmutable(Expression expression)
|
|
{
|
|
if (null == expression) { return false; }
|
|
switch (expression.NodeType)
|
|
{
|
|
case ExpressionType.New:
|
|
{
|
|
// support construction of primitive types
|
|
PrimitiveType primitiveType;
|
|
if (!ClrProviderManifest.Instance.TryGetPrimitiveType(TypeSystem.GetNonNullableType(expression.Type),
|
|
out primitiveType))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
case ExpressionType.Constant:
|
|
return true;
|
|
case ExpressionType.NewArrayInit:
|
|
// allow initialization of byte[] 'literals'
|
|
return (typeof(byte[]) == expression.Type);
|
|
case ExpressionType.Convert:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the node may be evaluated locally and whether
|
|
/// it is a variable. Assumes that all children are also variable client expressions.
|
|
/// </summary>
|
|
private bool IsClosureExpression(Expression expression)
|
|
{
|
|
if (null == expression) { return false; }
|
|
if (IsImmutable(expression)) { return true; }
|
|
if (ExpressionType.MemberAccess == expression.NodeType)
|
|
{
|
|
MemberExpression member = (MemberExpression)expression;
|
|
if (member.Member.MemberType == MemberTypes.Property)
|
|
{
|
|
return ExpressionConverter.CanFuncletizePropertyInfo((PropertyInfo)member.Member);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the node may be evaluated as a compiled query parameter.
|
|
/// Assumes that all children are also eligible compiled query parameters.
|
|
/// </summary>
|
|
private bool IsCompiledQueryParameterVariable(Expression expression)
|
|
{
|
|
if (null == expression) { return false; }
|
|
if (IsClosureExpression(expression)) { return true; }
|
|
if (ExpressionType.Parameter == expression.NodeType)
|
|
{
|
|
ParameterExpression parameter = (ParameterExpression)expression;
|
|
return _compiledQueryParameters.Contains(parameter);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether the given CLR type is legal for an ObjectParameter or constant
|
|
/// DbExpression.
|
|
/// </summary>
|
|
private bool TryGetTypeUsageForTerminal(Type type, out TypeUsage typeUsage)
|
|
{
|
|
EntityUtil.CheckArgumentNull(type, "type");
|
|
|
|
if (_rootContext.Perspective.TryGetTypeByName(TypeSystem.GetNonNullableType(type).FullName,
|
|
false, // bIgnoreCase
|
|
out typeUsage) &&
|
|
(TypeSemantics.IsScalarType(typeUsage)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
typeUsage = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the next available parameter name.
|
|
/// </summary>
|
|
internal string GenerateParameterName()
|
|
{
|
|
// To avoid collisions with user parameters (the full set is not
|
|
// known at this time) we plug together an 'unlikely' prefix and
|
|
// a number.
|
|
return String.Format(CultureInfo.InvariantCulture, "{0}{1}",
|
|
s_parameterPrefix,
|
|
_parameterNumber++);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Walks the expression tree and replaces client-evaluable expressions with constants
|
|
/// or QueryParameterExpressions.
|
|
/// </summary>
|
|
private sealed class FuncletizingVisitor : EntityExpressionVisitor
|
|
{
|
|
private readonly Funcletizer _funcletizer;
|
|
private readonly Func<Expression, bool> _isClientConstant;
|
|
private readonly Func<Expression, bool> _isClientVariable;
|
|
private readonly List<Func<bool>> _recompileRequiredDelegates = new List<Func<bool>>();
|
|
|
|
internal FuncletizingVisitor(
|
|
Funcletizer funcletizer,
|
|
Func<Expression, bool> isClientConstant,
|
|
Func<Expression, bool> isClientVariable)
|
|
{
|
|
EntityUtil.CheckArgumentNull(funcletizer, "funcletizer");
|
|
EntityUtil.CheckArgumentNull(isClientConstant, "isClientConstant");
|
|
EntityUtil.CheckArgumentNull(isClientVariable, "isClientVariable");
|
|
|
|
_funcletizer = funcletizer;
|
|
_isClientConstant = isClientConstant;
|
|
_isClientVariable = isClientVariable;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a delegate indicating (when called) whether a change has been identified
|
|
/// requiring a complete recompile of the query.
|
|
/// </summary>
|
|
internal Func<bool> GetRecompileRequiredFunction()
|
|
{
|
|
// assign list to local variable to avoid including the entire Funcletizer
|
|
// class in the closure environment
|
|
ReadOnlyCollection<Func<bool>> recompileRequiredDelegates = _recompileRequiredDelegates.AsReadOnly();
|
|
return () => recompileRequiredDelegates.Any(d => d());
|
|
}
|
|
|
|
internal override Expression Visit(Expression exp)
|
|
{
|
|
if (exp != null)
|
|
{
|
|
if (!_funcletizer._linqExpressionStack.Add(exp))
|
|
{
|
|
// This expression is already in the stack.
|
|
throw EntityUtil.InvalidOperation(Strings.ELinq_CycleDetected);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (_isClientConstant(exp))
|
|
{
|
|
return InlineValue(exp, false);
|
|
}
|
|
else if (_isClientVariable(exp))
|
|
{
|
|
TypeUsage queryParameterType;
|
|
if (_funcletizer.TryGetTypeUsageForTerminal(exp.Type, out queryParameterType))
|
|
{
|
|
DbParameterReferenceExpression parameterReference = queryParameterType.Parameter(_funcletizer.GenerateParameterName());
|
|
return new QueryParameterExpression(parameterReference, exp, _funcletizer._compiledQueryParameters);
|
|
}
|
|
else if (_funcletizer.IsCompiledQuery)
|
|
{
|
|
throw InvalidCompiledQueryParameterException(exp);
|
|
}
|
|
else
|
|
{
|
|
return InlineValue(exp, true);
|
|
}
|
|
}
|
|
return base.Visit(exp);
|
|
}
|
|
finally
|
|
{
|
|
_funcletizer._linqExpressionStack.Remove(exp);
|
|
}
|
|
}
|
|
return base.Visit(exp);
|
|
}
|
|
|
|
private static NotSupportedException InvalidCompiledQueryParameterException(Expression expression)
|
|
{
|
|
ParameterExpression parameterExp;
|
|
if (expression.NodeType == ExpressionType.Parameter)
|
|
{
|
|
parameterExp = (ParameterExpression)expression;
|
|
}
|
|
else
|
|
{
|
|
// If this is a simple query parameter (involving a single delegate parameter) report the
|
|
// type of that parameter. Otherwise, report the type of the part of the parameter.
|
|
HashSet<ParameterExpression> parameters = new HashSet<ParameterExpression>();
|
|
EntityExpressionVisitor.Visit(expression, (exp, baseVisit) =>
|
|
{
|
|
if (null != exp && exp.NodeType == ExpressionType.Parameter)
|
|
{
|
|
parameters.Add((ParameterExpression)exp);
|
|
}
|
|
return baseVisit(exp);
|
|
});
|
|
|
|
if (parameters.Count != 1)
|
|
{
|
|
return EntityUtil.NotSupported(Strings.CompiledELinq_UnsupportedParameterTypes(expression.Type.FullName));
|
|
}
|
|
|
|
parameterExp = parameters.Single();
|
|
}
|
|
|
|
if (parameterExp.Type.Equals(expression.Type))
|
|
{
|
|
// If the expression type is the same as the parameter type, indicate that the parameter type is not valid.
|
|
return EntityUtil.NotSupported(Strings.CompiledELinq_UnsupportedNamedParameterType(parameterExp.Name, parameterExp.Type.FullName));
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, indicate that using the specified parameter to produce a value of the expression's type is not supported in compiled query
|
|
return EntityUtil.NotSupported(Strings.CompiledELinq_UnsupportedNamedParameterUseAsType(parameterExp.Name, expression.Type.FullName));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compiles a delegate returning the value of the given expression.
|
|
/// </summary>
|
|
private Func<object> CompileExpression(Expression expression)
|
|
{
|
|
Func<object> func = Expression
|
|
.Lambda<Func<object>>(TypeSystem.EnsureType(expression, typeof(object)))
|
|
.Compile();
|
|
return func;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inlines a funcletizable expression. Queries and lambda expressions are expanded
|
|
/// inline. All other values become simple constants.
|
|
/// </summary>
|
|
private Expression InlineValue(Expression expression, bool recompileOnChange)
|
|
{
|
|
Func<object> getValue = null;
|
|
object value = null;
|
|
if (expression.NodeType == ExpressionType.Constant)
|
|
{
|
|
value = ((ConstantExpression)expression).Value;
|
|
}
|
|
else
|
|
{
|
|
bool fastPath = false;
|
|
//fastpath to process object query
|
|
if (expression.NodeType == ExpressionType.Convert)
|
|
{
|
|
var ue = (UnaryExpression)expression;
|
|
// The ObjectSet instance is wrapped inside Convert UnaryExpression in
|
|
// ElinqQueryState.GetExpression(). The block below identifies such an
|
|
// expression, makes sure the object query it contains is immutable and
|
|
// extracts the reference to the object query.
|
|
if (!recompileOnChange
|
|
&& ue.Operand.NodeType == ExpressionType.Constant
|
|
&& typeof(IQueryable).IsAssignableFrom(ue.Operand.Type))
|
|
{
|
|
value = ((ConstantExpression)ue.Operand).Value;
|
|
fastPath = true;
|
|
}
|
|
}
|
|
if (!fastPath)
|
|
{
|
|
getValue = CompileExpression(expression);
|
|
value = getValue();
|
|
}
|
|
}
|
|
|
|
Expression result = null;
|
|
ObjectQuery inlineQuery = value as ObjectQuery;
|
|
if (inlineQuery != null)
|
|
{
|
|
result = InlineObjectQuery(inlineQuery, expression.Type);
|
|
}
|
|
else
|
|
{
|
|
LambdaExpression lambda = value as LambdaExpression;
|
|
if (null != lambda)
|
|
{
|
|
result = InlineExpression(Expression.Quote(lambda));
|
|
}
|
|
else
|
|
{
|
|
// everything else is just a constant...
|
|
result = expression.NodeType == ExpressionType.Constant
|
|
? expression
|
|
: Expression.Constant(value, expression.Type);
|
|
}
|
|
}
|
|
|
|
if (recompileOnChange)
|
|
{
|
|
AddRecompileRequiredDelegates(getValue, value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void AddRecompileRequiredDelegates(Func<object> getValue, object value)
|
|
{
|
|
// Build a delegate that returns true when the inline value has changed.
|
|
// Outside of ObjectQuery, this amounts to a reference comparison.
|
|
ObjectQuery originalQuery = value as ObjectQuery;
|
|
if (null != originalQuery)
|
|
{
|
|
// For inline queries, we need to check merge options as well (it's mutable)
|
|
MergeOption? originalMergeOption = originalQuery.QueryState.UserSpecifiedMergeOption;
|
|
if (null == getValue)
|
|
{
|
|
_recompileRequiredDelegates.Add(() => originalQuery.QueryState.UserSpecifiedMergeOption != originalMergeOption);
|
|
}
|
|
else
|
|
{
|
|
_recompileRequiredDelegates.Add(() =>
|
|
{
|
|
ObjectQuery currentQuery = getValue() as ObjectQuery;
|
|
return !object.ReferenceEquals(originalQuery, currentQuery) ||
|
|
currentQuery.QueryState.UserSpecifiedMergeOption != originalMergeOption;
|
|
});
|
|
|
|
}
|
|
}
|
|
else if (null != getValue)
|
|
{
|
|
_recompileRequiredDelegates.Add(() => !object.ReferenceEquals(value, getValue()));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the appropriate LINQ expression for an inline ObjectQuery instance.
|
|
/// </summary>
|
|
private Expression InlineObjectQuery(ObjectQuery inlineQuery, Type expressionType)
|
|
{
|
|
EntityUtil.CheckArgumentNull(inlineQuery, "inlineQuery");
|
|
|
|
Expression queryExpression;
|
|
if (_funcletizer._mode == Mode.CompiledQueryLockdown)
|
|
{
|
|
// In the lockdown phase, we don't chase down inline object queries because
|
|
// we don't yet know what the object context is supposed to be.
|
|
queryExpression = Expression.Constant(inlineQuery, expressionType);
|
|
}
|
|
else
|
|
{
|
|
if (!object.ReferenceEquals(_funcletizer._rootContext, inlineQuery.QueryState.ObjectContext))
|
|
{
|
|
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedDifferentContexts);
|
|
}
|
|
|
|
queryExpression = inlineQuery.GetExpression();
|
|
|
|
// If it's not an entity-sql (terminal) query, recursively process
|
|
if (!(inlineQuery.QueryState is EntitySqlQueryState))
|
|
{
|
|
queryExpression = InlineExpression(queryExpression);
|
|
}
|
|
|
|
queryExpression = TypeSystem.EnsureType(queryExpression, expressionType);
|
|
}
|
|
|
|
return queryExpression;
|
|
}
|
|
|
|
private Expression InlineExpression(Expression exp)
|
|
{
|
|
Func<bool> inlineExpressionRequiresRecompile;
|
|
exp = _funcletizer.Funcletize(exp, out inlineExpressionRequiresRecompile);
|
|
if (!_funcletizer.IsCompiledQuery)
|
|
{
|
|
_recompileRequiredDelegates.Add(inlineExpressionRequiresRecompile);
|
|
}
|
|
return exp;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A LINQ expression corresponding to a query parameter.
|
|
/// </summary>
|
|
internal sealed class QueryParameterExpression : Expression
|
|
{
|
|
private readonly DbParameterReferenceExpression _parameterReference;
|
|
private readonly Type _type;
|
|
private readonly Expression _funcletizedExpression;
|
|
private readonly IEnumerable<ParameterExpression> _compiledQueryParameters;
|
|
private Delegate _cachedDelegate;
|
|
|
|
internal QueryParameterExpression(
|
|
DbParameterReferenceExpression parameterReference,
|
|
Expression funcletizedExpression,
|
|
IEnumerable<ParameterExpression> compiledQueryParameters)
|
|
{
|
|
EntityUtil.CheckArgumentNull(parameterReference, "parameterReference");
|
|
EntityUtil.CheckArgumentNull(funcletizedExpression, "funcletizedExpression");
|
|
_compiledQueryParameters = compiledQueryParameters ?? Enumerable.Empty<ParameterExpression>();
|
|
_parameterReference = parameterReference;
|
|
_type = funcletizedExpression.Type;
|
|
_funcletizedExpression = funcletizedExpression;
|
|
_cachedDelegate = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current value of the parameter given (optional) compiled query arguments.
|
|
/// </summary>
|
|
internal object EvaluateParameter(object[] arguments)
|
|
{
|
|
if (_cachedDelegate == null)
|
|
{
|
|
if (_funcletizedExpression.NodeType == ExpressionType.Constant)
|
|
{
|
|
return ((ConstantExpression)_funcletizedExpression).Value;
|
|
}
|
|
ConstantExpression ce;
|
|
if (TryEvaluatePath(_funcletizedExpression, out ce))
|
|
{
|
|
return ce.Value;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if (_cachedDelegate == null)
|
|
{
|
|
// Get the Func<> type for the property evaluator
|
|
Type delegateType = TypeSystem.GetDelegateType(_compiledQueryParameters.Select(p => p.Type), _type);
|
|
|
|
// Now compile delegate for the funcletized expression
|
|
_cachedDelegate = Expression.Lambda(delegateType, _funcletizedExpression, _compiledQueryParameters).Compile();
|
|
}
|
|
return _cachedDelegate.DynamicInvoke(arguments);
|
|
}
|
|
catch (TargetInvocationException e)
|
|
{
|
|
throw e.InnerException;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create QueryParameterExpression based on this one, but with the funcletized expression
|
|
/// wrapped by the given method
|
|
/// </summary>
|
|
/// <param name="method"></param>
|
|
/// <returns></returns>
|
|
internal QueryParameterExpression EscapeParameterForLike(Func<string, string> method)
|
|
{
|
|
Expression wrappedExpression = Expression.Invoke(Expression.Constant(method), this._funcletizedExpression);
|
|
return new QueryParameterExpression(this._parameterReference, wrappedExpression, this._compiledQueryParameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the parameter reference for the parameter.
|
|
/// </summary>
|
|
internal DbParameterReferenceExpression ParameterReference
|
|
{
|
|
get { return _parameterReference; }
|
|
}
|
|
|
|
public override Type Type
|
|
{
|
|
get { return _type; }
|
|
}
|
|
|
|
public override ExpressionType NodeType
|
|
{
|
|
get { return EntityExpressionVisitor.CustomExpression; }
|
|
}
|
|
|
|
private bool TryEvaluatePath(Expression expression, out ConstantExpression constantExpression)
|
|
{
|
|
MemberExpression me = expression as MemberExpression;
|
|
constantExpression = null;
|
|
if (me != null)
|
|
{
|
|
Stack<MemberExpression> stack = new Stack<MemberExpression>();
|
|
stack.Push(me);
|
|
while ((me = me.Expression as MemberExpression) != null)
|
|
{
|
|
stack.Push(me);
|
|
}
|
|
me = stack.Pop();
|
|
ConstantExpression ce = me.Expression as ConstantExpression;
|
|
if (ce != null)
|
|
{
|
|
object memberVal;
|
|
if (!TryGetFieldOrPropertyValue(me, ((ConstantExpression)me.Expression).Value, out memberVal))
|
|
{
|
|
return false;
|
|
}
|
|
if (stack.Count > 0)
|
|
{
|
|
foreach (var rec in stack)
|
|
{
|
|
if (!TryGetFieldOrPropertyValue(rec, memberVal, out memberVal))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
constantExpression = Expression.Constant(memberVal, expression.Type);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryGetFieldOrPropertyValue(MemberExpression me, object instance, out object memberValue)
|
|
{
|
|
bool result = false;
|
|
memberValue = null;
|
|
|
|
try
|
|
{
|
|
if (me.Member.MemberType == MemberTypes.Field)
|
|
{
|
|
memberValue = ((FieldInfo)me.Member).GetValue(instance);
|
|
result = true;
|
|
}
|
|
else if (me.Member.MemberType == MemberTypes.Property)
|
|
{
|
|
memberValue = ((PropertyInfo)me.Member).GetValue(instance, null);
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
catch (TargetInvocationException ex)
|
|
{
|
|
throw ex.InnerException;
|
|
}
|
|
}
|
|
}
|
|
}
|