Funcletizer.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataEntity / System / Data / Objects / ELinq / Funcletizer.cs / 1305376 / Funcletizer.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Linq; 
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Data.Metadata.Edm; 
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder; 
using System.Diagnostics; 
using System.Globalization;
using System.Reflection; 
using System.Data.Entity;
using System.Security;
namespace System.Data.Objects.ELinq
{ 
    /// 
    /// 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.
    ///  
    internal sealed class Funcletizer
    { 
        // Compiled query information 
        private readonly ParameterExpression _rootContextParameter;
        private readonly ObjectContext _rootContext; 
        private readonly ConstantExpression _rootContextExpression;
        private readonly ReadOnlyCollection _compiledQueryParameters;
        private readonly Mode _mode;
        private readonly HashSet _linqExpressionStack = new HashSet(); 

        // Object parameters 
        private static readonly string s_parameterPrefix = "p__linq__"; 
        private long _parameterNumber;
 
        private Funcletizer(
            Mode mode,
            ObjectContext rootContext,
            ParameterExpression rootContextParameter, 
            ReadOnlyCollection 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 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; } 
        }
 
        ///  
        /// Performs funcletization on the given expression. Also returns a delegates that can be used
        /// to determine if the entire tree needs to be recompiled. 
        /// 
        internal Expression Funcletize(Expression expression, out Func 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 isClientConstant;
            Func 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; 
        } 

        ///  
        /// Replaces context parameter (e.g. 'ctx' in CompiledQuery.Compile(ctx => ctx.Products)) with constant
        /// containing the object context.
        /// 
        private Expression ReplaceRootContextParameter(Expression expression) 
        {
            if (null != _rootContextExpression) 
            { 
                return EntityExpressionVisitor.Visit(
                    expression, (exp, baseVisit) => 
                    exp == _rootContextParameter ? _rootContextExpression : baseVisit(exp));
            }
            else
            { 
                return expression;
            } 
        } 

        ///  
        /// Returns a function indicating whether the given expression and all of its children satisfy the
        /// 'localCriterion'.
        /// 
        private static Func Nominate(Expression expression, Func localCriterion) 
        {
            EntityUtil.CheckArgumentNull(localCriterion, "localCriterion"); 
            HashSet candidates = new HashSet(); 
            bool cannotBeNominated = false;
            Func, 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,
        } 
 
        /// 
        /// Determines whether the node may be evaluated locally and whether 
        /// it is a constant. Assumes that all children are also client expressions.
        /// 
        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; 
            }
        }

        ///  
        /// Determines whether the node may be evaluated locally and whether
        /// it is a variable. Assumes that all children are also variable client expressions. 
        ///  
        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;
        } 

        ///  
        /// Determines whether the node may be evaluated as a compiled query parameter. 
        /// Assumes that all children are also eligible compiled query parameters.
        ///  
        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;
        }

        ///  
        /// Determine whether the given CLR type is legal for an ObjectParameter or constant
        /// DbExpression. 
        ///  
        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.IsPrimitiveType(typeUsage) || TypeSemantics.IsEnumerationType(typeUsage))) 
            { 
                return true;
            } 

            typeUsage = null;
            return false;
        } 

        ///  
        /// Creates the next available parameter name. 
        /// 
        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++); 
        }
 
        /// 
        /// Walks the expression tree and replaces client components with constants
        /// or QueryParameterExpressions.
        ///  
        private sealed class FuncletizingVisitor : EntityExpressionVisitor
        { 
            private readonly Funcletizer _funcletizer; 
            private readonly Func _isClientConstant;
            private readonly Func _isClientVariable; 
            private readonly List> _recompileRequiredDelegates = new List>();

            internal FuncletizingVisitor(
                Funcletizer funcletizer, 
                Func isClientConstant,
                Func isClientVariable) 
            { 
                EntityUtil.CheckArgumentNull(funcletizer, "funcletizer");
                EntityUtil.CheckArgumentNull(isClientConstant, "isClientConstant"); 
                EntityUtil.CheckArgumentNull(isClientVariable, "isClientVariable");

                _funcletizer = funcletizer;
                _isClientConstant = isClientConstant; 
                _isClientVariable = isClientVariable;
            } 
 
            /// 
            /// Returns a delegate indicating (when called) whether a change has been identified 
            /// requiring a complete recompile of the query.
            /// 
            internal Func GetRecompileRequiredFunction()
            { 
                // assign list to local variable to avoid including the entire Funcletizer
                // class in the closure environment 
                ReadOnlyCollection> 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 parameters = new HashSet();
                    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));
                }
            }
 
            /// 
            /// Compiles a delegate returning the value of the given expression. 
            ///  
            private Func CompileExpression(Expression expression)
            { 
                Func func = Expression
                    .Lambda>(TypeSystem.EnsureType(expression, typeof(object)))
                    .Compile();
                return func; 
            }
 
            ///  
            /// Inlines a funcletizable expression. Queries and lambda expressions are expanded
            /// inline. All other values become simple constants. 
            /// 
            private Expression InlineValue(Expression expression, bool recompileOnChange)
            {
                Func getValue; 
                object value;
                if (expression.NodeType == ExpressionType.Constant) 
                { 
                    value = ((ConstantExpression)expression).Value;
                    getValue = null; 
                }
                else
                {
                    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 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()));
                }
            }
 
            /// 
            /// Gets the appropriate LINQ expression for an inline ObjectQuery instance. Handles 
            /// merge options and 
            /// 
            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 inlineExpressionRequiresRecompile; 
                exp = _funcletizer.Funcletize(exp, out inlineExpressionRequiresRecompile);
                if (!_funcletizer.IsCompiledQuery) 
                {
                    _recompileRequiredDelegates.Add(inlineExpressionRequiresRecompile);
                }
                return exp; 
            }
        } 
    } 

    ///  
    /// A LINQ expression corresponding to a query parameter.
    /// 
    internal sealed class QueryParameterExpression : Expression
    { 
        private readonly Delegate _evaluateParameter;
        private readonly LambdaExpression _lambda; 
        private readonly DbParameterReferenceExpression _parameterReference; 
        private readonly Type _type;
        internal QueryParameterExpression( 
            DbParameterReferenceExpression parameterReference,
            Expression funcletizedExpression,
            IEnumerable compiledQueryParameters)
        { 
            EntityUtil.CheckArgumentNull(parameterReference, "parameterReference");
            EntityUtil.CheckArgumentNull(funcletizedExpression, "funcletizedExpression"); 
            compiledQueryParameters = compiledQueryParameters ?? Enumerable.Empty(); 
            _parameterReference = parameterReference;
            _type = funcletizedExpression.Type; 

            // 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
            _lambda = Expression.Lambda(delegateType, funcletizedExpression, compiledQueryParameters); 
            _evaluateParameter = _lambda.Compile(); 
        }
 
        /// 
        /// Gets the current value of the parameter given (optional) compiled query arguments.
        /// 
        internal object EvaluateParameter(object[] arguments) 
        {
            // If the user does not have MemberAccess permission, recompile the delegate to make sure 
            // they have the necessary permissions to compile this expression. 
            if (!LightweightCodeGenerator.HasMemberAccessReflectionPermission())
            { 
                _lambda.Compile();
            }

            try 
            {
                return _evaluateParameter.DynamicInvoke(arguments); 
            } 
            catch (TargetInvocationException e)
            { 
                throw e.InnerException;
            }
        }
 
        /// 
        /// Create QueryParameterExpression based on this one, but with the funcletized expression 
        /// wrapped by the given method 
        /// 
        ///  
        /// 
        internal QueryParameterExpression EscapeParameterForLike(Func method)
        {
            Expression wrappedExpression = Expression.Invoke(Expression.Constant(method), this._lambda.Body); 
            return new QueryParameterExpression(this._parameterReference, wrappedExpression, this._lambda.Parameters);
        } 
 
        /// 
        /// Gets the parameter reference for the parameter. 
        /// 
        internal DbParameterReferenceExpression ParameterReference
        {
            get { return _parameterReference; } 
        }
 
        public override Type Type 
        {
            get { return _type; } 
        }

        public override ExpressionType NodeType
        { 
            get { return EntityExpressionVisitor.CustomExpression; }
        } 
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Linq; 
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Data.Metadata.Edm; 
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder; 
using System.Diagnostics; 
using System.Globalization;
using System.Reflection; 
using System.Data.Entity;
using System.Security;
namespace System.Data.Objects.ELinq
{ 
    /// 
    /// 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.
    ///  
    internal sealed class Funcletizer
    { 
        // Compiled query information 
        private readonly ParameterExpression _rootContextParameter;
        private readonly ObjectContext _rootContext; 
        private readonly ConstantExpression _rootContextExpression;
        private readonly ReadOnlyCollection _compiledQueryParameters;
        private readonly Mode _mode;
        private readonly HashSet _linqExpressionStack = new HashSet(); 

        // Object parameters 
        private static readonly string s_parameterPrefix = "p__linq__"; 
        private long _parameterNumber;
 
        private Funcletizer(
            Mode mode,
            ObjectContext rootContext,
            ParameterExpression rootContextParameter, 
            ReadOnlyCollection 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 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; } 
        }
 
        ///  
        /// Performs funcletization on the given expression. Also returns a delegates that can be used
        /// to determine if the entire tree needs to be recompiled. 
        /// 
        internal Expression Funcletize(Expression expression, out Func 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 isClientConstant;
            Func 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; 
        } 

        ///  
        /// Replaces context parameter (e.g. 'ctx' in CompiledQuery.Compile(ctx => ctx.Products)) with constant
        /// containing the object context.
        /// 
        private Expression ReplaceRootContextParameter(Expression expression) 
        {
            if (null != _rootContextExpression) 
            { 
                return EntityExpressionVisitor.Visit(
                    expression, (exp, baseVisit) => 
                    exp == _rootContextParameter ? _rootContextExpression : baseVisit(exp));
            }
            else
            { 
                return expression;
            } 
        } 

        ///  
        /// Returns a function indicating whether the given expression and all of its children satisfy the
        /// 'localCriterion'.
        /// 
        private static Func Nominate(Expression expression, Func localCriterion) 
        {
            EntityUtil.CheckArgumentNull(localCriterion, "localCriterion"); 
            HashSet candidates = new HashSet(); 
            bool cannotBeNominated = false;
            Func, 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,
        } 
 
        /// 
        /// Determines whether the node may be evaluated locally and whether 
        /// it is a constant. Assumes that all children are also client expressions.
        /// 
        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; 
            }
        }

        ///  
        /// Determines whether the node may be evaluated locally and whether
        /// it is a variable. Assumes that all children are also variable client expressions. 
        ///  
        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;
        } 

        ///  
        /// Determines whether the node may be evaluated as a compiled query parameter. 
        /// Assumes that all children are also eligible compiled query parameters.
        ///  
        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;
        }

        ///  
        /// Determine whether the given CLR type is legal for an ObjectParameter or constant
        /// DbExpression. 
        ///  
        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.IsPrimitiveType(typeUsage) || TypeSemantics.IsEnumerationType(typeUsage))) 
            { 
                return true;
            } 

            typeUsage = null;
            return false;
        } 

        ///  
        /// Creates the next available parameter name. 
        /// 
        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++); 
        }
 
        /// 
        /// Walks the expression tree and replaces client components with constants
        /// or QueryParameterExpressions.
        ///  
        private sealed class FuncletizingVisitor : EntityExpressionVisitor
        { 
            private readonly Funcletizer _funcletizer; 
            private readonly Func _isClientConstant;
            private readonly Func _isClientVariable; 
            private readonly List> _recompileRequiredDelegates = new List>();

            internal FuncletizingVisitor(
                Funcletizer funcletizer, 
                Func isClientConstant,
                Func isClientVariable) 
            { 
                EntityUtil.CheckArgumentNull(funcletizer, "funcletizer");
                EntityUtil.CheckArgumentNull(isClientConstant, "isClientConstant"); 
                EntityUtil.CheckArgumentNull(isClientVariable, "isClientVariable");

                _funcletizer = funcletizer;
                _isClientConstant = isClientConstant; 
                _isClientVariable = isClientVariable;
            } 
 
            /// 
            /// Returns a delegate indicating (when called) whether a change has been identified 
            /// requiring a complete recompile of the query.
            /// 
            internal Func GetRecompileRequiredFunction()
            { 
                // assign list to local variable to avoid including the entire Funcletizer
                // class in the closure environment 
                ReadOnlyCollection> 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 parameters = new HashSet();
                    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));
                }
            }
 
            /// 
            /// Compiles a delegate returning the value of the given expression. 
            ///  
            private Func CompileExpression(Expression expression)
            { 
                Func func = Expression
                    .Lambda>(TypeSystem.EnsureType(expression, typeof(object)))
                    .Compile();
                return func; 
            }
 
            ///  
            /// Inlines a funcletizable expression. Queries and lambda expressions are expanded
            /// inline. All other values become simple constants. 
            /// 
            private Expression InlineValue(Expression expression, bool recompileOnChange)
            {
                Func getValue; 
                object value;
                if (expression.NodeType == ExpressionType.Constant) 
                { 
                    value = ((ConstantExpression)expression).Value;
                    getValue = null; 
                }
                else
                {
                    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 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()));
                }
            }
 
            /// 
            /// Gets the appropriate LINQ expression for an inline ObjectQuery instance. Handles 
            /// merge options and 
            /// 
            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 inlineExpressionRequiresRecompile; 
                exp = _funcletizer.Funcletize(exp, out inlineExpressionRequiresRecompile);
                if (!_funcletizer.IsCompiledQuery) 
                {
                    _recompileRequiredDelegates.Add(inlineExpressionRequiresRecompile);
                }
                return exp; 
            }
        } 
    } 

    ///  
    /// A LINQ expression corresponding to a query parameter.
    /// 
    internal sealed class QueryParameterExpression : Expression
    { 
        private readonly Delegate _evaluateParameter;
        private readonly LambdaExpression _lambda; 
        private readonly DbParameterReferenceExpression _parameterReference; 
        private readonly Type _type;
        internal QueryParameterExpression( 
            DbParameterReferenceExpression parameterReference,
            Expression funcletizedExpression,
            IEnumerable compiledQueryParameters)
        { 
            EntityUtil.CheckArgumentNull(parameterReference, "parameterReference");
            EntityUtil.CheckArgumentNull(funcletizedExpression, "funcletizedExpression"); 
            compiledQueryParameters = compiledQueryParameters ?? Enumerable.Empty(); 
            _parameterReference = parameterReference;
            _type = funcletizedExpression.Type; 

            // 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
            _lambda = Expression.Lambda(delegateType, funcletizedExpression, compiledQueryParameters); 
            _evaluateParameter = _lambda.Compile(); 
        }
 
        /// 
        /// Gets the current value of the parameter given (optional) compiled query arguments.
        /// 
        internal object EvaluateParameter(object[] arguments) 
        {
            // If the user does not have MemberAccess permission, recompile the delegate to make sure 
            // they have the necessary permissions to compile this expression. 
            if (!LightweightCodeGenerator.HasMemberAccessReflectionPermission())
            { 
                _lambda.Compile();
            }

            try 
            {
                return _evaluateParameter.DynamicInvoke(arguments); 
            } 
            catch (TargetInvocationException e)
            { 
                throw e.InnerException;
            }
        }
 
        /// 
        /// Create QueryParameterExpression based on this one, but with the funcletized expression 
        /// wrapped by the given method 
        /// 
        ///  
        /// 
        internal QueryParameterExpression EscapeParameterForLike(Func method)
        {
            Expression wrappedExpression = Expression.Invoke(Expression.Constant(method), this._lambda.Body); 
            return new QueryParameterExpression(this._parameterReference, wrappedExpression, this._lambda.Parameters);
        } 
 
        /// 
        /// Gets the parameter reference for the parameter. 
        /// 
        internal DbParameterReferenceExpression ParameterReference
        {
            get { return _parameterReference; } 
        }
 
        public override Type Type 
        {
            get { return _type; } 
        }

        public override ExpressionType NodeType
        { 
            get { return EntityExpressionVisitor.CustomExpression; }
        } 
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK