1 //---------------------------------------------------------------------
2 // <copyright file="Funcletizer.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 
10 namespace System.Data.Objects.ELinq
11 {
12     using System.Collections.Generic;
13     using System.Collections.ObjectModel;
14     using System.Data.Common.CommandTrees;
15     using System.Data.Common.CommandTrees.ExpressionBuilder;
16     using System.Data.Entity;
17     using System.Data.Metadata.Edm;
18     using System.Diagnostics;
19     using System.Globalization;
20     using System.Linq;
21     using System.Linq.Expressions;
22     using System.Reflection;
23 
24     /// <summary>
25     /// Determines which leaves of a LINQ expression tree should be evaluated locally before
26     /// sending a query to the store. These sub-expressions may map to query parameters (e.g. local variables),
27     /// to constants (e.g. literals 'new DateTime(2008, 1, 1)') or query sub-expression
28     /// (e.g. 'context.Products'). Parameter expressions are replaced with QueryParameterExpression
29     /// nodes. All other elements are swapped in place with either expanded expressions (for sub-queries)
30     /// or constants. Where the expression includes mutable state that may influence the translation
31     /// to a query, a Func(Of Boolean) delegate is returned indicating when a recompilation is necessary.
32     /// </summary>
33     internal sealed class Funcletizer
34     {
35         // Compiled query information
36         private readonly ParameterExpression _rootContextParameter;
37         private readonly ObjectContext _rootContext;
38         private readonly ConstantExpression _rootContextExpression;
39         private readonly ReadOnlyCollection<ParameterExpression> _compiledQueryParameters;
40         private readonly Mode _mode;
41         private readonly HashSet<Expression> _linqExpressionStack = new HashSet<Expression>();
42 
43         // Object parameters
44         private static readonly string s_parameterPrefix = "p__linq__";
45         private long _parameterNumber;
46 
Funcletizer( Mode mode, ObjectContext rootContext, ParameterExpression rootContextParameter, ReadOnlyCollection<ParameterExpression> compiledQueryParameters)47         private Funcletizer(
48             Mode mode,
49             ObjectContext rootContext,
50             ParameterExpression rootContextParameter,
51             ReadOnlyCollection<ParameterExpression> compiledQueryParameters)
52         {
53             _mode = mode;
54             _rootContext = rootContext;
55             _rootContextParameter = rootContextParameter;
56             _compiledQueryParameters = compiledQueryParameters;
57             if (null != _rootContextParameter && null != _rootContext)
58             {
59                 _rootContextExpression = Expression.Constant(_rootContext);
60             }
61         }
62 
CreateCompiledQueryEvaluationFuncletizer( ObjectContext rootContext, ParameterExpression rootContextParameter, ReadOnlyCollection<ParameterExpression> compiledQueryParameters)63         internal static Funcletizer CreateCompiledQueryEvaluationFuncletizer(
64             ObjectContext rootContext,
65             ParameterExpression rootContextParameter,
66             ReadOnlyCollection<ParameterExpression> compiledQueryParameters)
67         {
68             EntityUtil.CheckArgumentNull(rootContext, "rootContext");
69             EntityUtil.CheckArgumentNull(rootContextParameter, "rootContextParameter");
70             EntityUtil.CheckArgumentNull(compiledQueryParameters, "compiledQueryParameters");
71 
72             return new Funcletizer(Mode.CompiledQueryEvaluation, rootContext, rootContextParameter, compiledQueryParameters);
73         }
74 
CreateCompiledQueryLockdownFuncletizer()75         internal static Funcletizer CreateCompiledQueryLockdownFuncletizer()
76         {
77             return new Funcletizer(Mode.CompiledQueryLockdown, null, null, null);
78         }
79 
CreateQueryFuncletizer(ObjectContext rootContext)80         internal static Funcletizer CreateQueryFuncletizer(ObjectContext rootContext)
81         {
82             EntityUtil.CheckArgumentNull(rootContext, "rootContext");
83 
84             return new Funcletizer(Mode.ConventionalQuery, rootContext, null, null);
85         }
86 
87         internal ObjectContext RootContext
88         {
89             get { return _rootContext; }
90         }
91 
92         internal ParameterExpression RootContextParameter
93         {
94             get { return _rootContextParameter; }
95         }
96 
97         internal ConstantExpression RootContextExpression
98         {
99             get { return _rootContextExpression; }
100         }
101 
102         internal bool IsCompiledQuery
103         {
104             get { return _mode == Mode.CompiledQueryEvaluation || _mode == Mode.CompiledQueryLockdown; }
105         }
106 
107         /// <summary>
108         /// Performs funcletization on the given expression. Also returns a delegates that can be used
109         /// to determine if the entire tree needs to be recompiled.
110         /// </summary>
Funcletize(Expression expression, out Func<bool> recompileRequired)111         internal Expression Funcletize(Expression expression, out Func<bool> recompileRequired)
112         {
113             EntityUtil.CheckArgumentNull(expression, "expression");
114 
115             // Find all candidates for funcletization. Some sub-expressions are reduced to constants,
116             // others are reduced to variables. The rules vary based on the _mode.
117             Func<Expression, bool> isClientConstant;
118             Func<Expression, bool> isClientVariable;
119 
120             expression = ReplaceRootContextParameter(expression);
121 
122             if (_mode == Mode.CompiledQueryEvaluation)
123             {
124                 // We lock down closure expressions for compiled queries, so everything is either
125                 // a constant or a query parameter produced from the explicit parameters to the
126                 // compiled query delegate.
127                 isClientConstant = Nominate(expression, this.IsClosureExpression);
128                 isClientVariable = Nominate(expression, this.IsCompiledQueryParameterVariable);
129             }
130             else if (_mode == Mode.CompiledQueryLockdown)
131             {
132                 // When locking down a compiled query, we can evaluate all closure expressions.
133                 isClientConstant = Nominate(expression, this.IsClosureExpression);
134                 isClientVariable = (exp) => false;
135             }
136             else
137             {
138                 Debug.Assert(_mode == Mode.ConventionalQuery, "No other options...");
139 
140                 // There are no variable parameters outside of compiled queries, so everything is
141                 // either a constant or a closure expression.
142                 isClientConstant = Nominate(expression, this.IsImmutable);
143                 isClientVariable = Nominate(expression, this.IsClosureExpression);
144             }
145 
146             // Now rewrite given nomination functions
147             FuncletizingVisitor visitor = new FuncletizingVisitor(this, isClientConstant, isClientVariable);
148             Expression result = visitor.Visit(expression);
149             recompileRequired = visitor.GetRecompileRequiredFunction();
150 
151             return result;
152         }
153 
154         /// <summary>
155         /// Replaces context parameter (e.g. 'ctx' in CompiledQuery.Compile(ctx => ctx.Products)) with constant
156         /// containing the object context.
157         /// </summary>
ReplaceRootContextParameter(Expression expression)158         private Expression ReplaceRootContextParameter(Expression expression)
159         {
160             if (null != _rootContextExpression)
161             {
162                 return EntityExpressionVisitor.Visit(
163                     expression, (exp, baseVisit) =>
164                     exp == _rootContextParameter ? _rootContextExpression : baseVisit(exp));
165             }
166             else
167             {
168                 return expression;
169             }
170         }
171 
172         /// <summary>
173         /// Returns a function indicating whether the given expression and all of its children satisfy the
174         /// 'localCriterion'.
175         /// </summary>
Nominate(Expression expression, Func<Expression, bool> localCriterion)176         private static Func<Expression, bool> Nominate(Expression expression, Func<Expression, bool> localCriterion)
177         {
178             EntityUtil.CheckArgumentNull(localCriterion, "localCriterion");
179             HashSet<Expression> candidates = new HashSet<Expression>();
180             bool cannotBeNominated = false;
181             Func<Expression, Func<Expression, Expression>, Expression> visit = (exp, baseVisit) =>
182                 {
183                     if (exp != null)
184                     {
185                         bool saveCannotBeNominated = cannotBeNominated;
186                         cannotBeNominated = false;
187                         baseVisit(exp);
188                         if (!cannotBeNominated)
189                         {
190                             // everyone below me can be nominated, so
191                             // see if this one can be also
192                             if (localCriterion(exp))
193                             {
194                                 candidates.Add(exp);
195                             }
196                             else
197                             {
198                                 cannotBeNominated = true;
199                             }
200                         }
201                         cannotBeNominated |= saveCannotBeNominated;
202                     }
203                     return exp;
204                 };
205             EntityExpressionVisitor.Visit(expression, visit);
206             return candidates.Contains;
207         }
208 
209         private enum Mode
210         {
211             CompiledQueryLockdown,
212             CompiledQueryEvaluation,
213             ConventionalQuery,
214         }
215 
216         /// <summary>
217         /// Determines whether the node may be evaluated locally and whether
218         /// it is a constant. Assumes that all children are also client expressions.
219         /// </summary>
IsImmutable(Expression expression)220         private bool IsImmutable(Expression expression)
221         {
222             if (null == expression) { return false; }
223             switch (expression.NodeType)
224             {
225                 case ExpressionType.New:
226                     {
227                         // support construction of primitive types
228                         PrimitiveType primitiveType;
229                         if (!ClrProviderManifest.Instance.TryGetPrimitiveType(TypeSystem.GetNonNullableType(expression.Type),
230                             out primitiveType))
231                         {
232                             return false;
233                         }
234                         return true;
235                     }
236                 case ExpressionType.Constant:
237                     return true;
238                 case ExpressionType.NewArrayInit:
239                     // allow initialization of byte[] 'literals'
240                     return (typeof(byte[]) == expression.Type);
241                 case ExpressionType.Convert:
242                     return true;
243                 default:
244                     return false;
245             }
246         }
247 
248         /// <summary>
249         /// Determines whether the node may be evaluated locally and whether
250         /// it is a variable. Assumes that all children are also variable client expressions.
251         /// </summary>
IsClosureExpression(Expression expression)252         private bool IsClosureExpression(Expression expression)
253         {
254             if (null == expression) { return false; }
255             if (IsImmutable(expression)) { return true; }
256             if (ExpressionType.MemberAccess == expression.NodeType)
257             {
258                 MemberExpression member = (MemberExpression)expression;
259                 if (member.Member.MemberType == MemberTypes.Property)
260                 {
261                     return ExpressionConverter.CanFuncletizePropertyInfo((PropertyInfo)member.Member);
262                 }
263                 return true;
264             }
265             return false;
266         }
267 
268         /// <summary>
269         /// Determines whether the node may be evaluated as a compiled query parameter.
270         /// Assumes that all children are also eligible compiled query parameters.
271         /// </summary>
IsCompiledQueryParameterVariable(Expression expression)272         private bool IsCompiledQueryParameterVariable(Expression expression)
273         {
274             if (null == expression) { return false; }
275             if (IsClosureExpression(expression)) { return true; }
276             if (ExpressionType.Parameter == expression.NodeType)
277             {
278                 ParameterExpression parameter = (ParameterExpression)expression;
279                 return _compiledQueryParameters.Contains(parameter);
280             }
281             return false;
282         }
283 
284         /// <summary>
285         /// Determine whether the given CLR type is legal for an ObjectParameter or constant
286         /// DbExpression.
287         /// </summary>
TryGetTypeUsageForTerminal(Type type, out TypeUsage typeUsage)288         private bool TryGetTypeUsageForTerminal(Type type, out TypeUsage typeUsage)
289         {
290             EntityUtil.CheckArgumentNull(type, "type");
291 
292             if (_rootContext.Perspective.TryGetTypeByName(TypeSystem.GetNonNullableType(type).FullName,
293                 false, // bIgnoreCase
294                 out typeUsage) &&
295                 (TypeSemantics.IsScalarType(typeUsage)))
296             {
297                 return true;
298             }
299 
300             typeUsage = null;
301             return false;
302         }
303 
304         /// <summary>
305         /// Creates the next available parameter name.
306         /// </summary>
GenerateParameterName()307         internal string GenerateParameterName()
308         {
309             // To avoid collisions with user parameters (the full set is not
310             // known at this time) we plug together an 'unlikely' prefix and
311             // a number.
312             return String.Format(CultureInfo.InvariantCulture, "{0}{1}",
313                 s_parameterPrefix,
314                 _parameterNumber++);
315         }
316 
317         /// <summary>
318         /// Walks the expression tree and replaces client-evaluable expressions with constants
319         /// or QueryParameterExpressions.
320         /// </summary>
321         private sealed class FuncletizingVisitor : EntityExpressionVisitor
322         {
323             private readonly Funcletizer _funcletizer;
324             private readonly Func<Expression, bool> _isClientConstant;
325             private readonly Func<Expression, bool> _isClientVariable;
326             private readonly List<Func<bool>> _recompileRequiredDelegates = new List<Func<bool>>();
327 
FuncletizingVisitor( Funcletizer funcletizer, Func<Expression, bool> isClientConstant, Func<Expression, bool> isClientVariable)328             internal FuncletizingVisitor(
329                 Funcletizer funcletizer,
330                 Func<Expression, bool> isClientConstant,
331                 Func<Expression, bool> isClientVariable)
332             {
333                 EntityUtil.CheckArgumentNull(funcletizer, "funcletizer");
334                 EntityUtil.CheckArgumentNull(isClientConstant, "isClientConstant");
335                 EntityUtil.CheckArgumentNull(isClientVariable, "isClientVariable");
336 
337                 _funcletizer = funcletizer;
338                 _isClientConstant = isClientConstant;
339                 _isClientVariable = isClientVariable;
340             }
341 
342             /// <summary>
343             /// Returns a delegate indicating (when called) whether a change has been identified
344             /// requiring a complete recompile of the query.
345             /// </summary>
GetRecompileRequiredFunction()346             internal Func<bool> GetRecompileRequiredFunction()
347             {
348                 // assign list to local variable to avoid including the entire Funcletizer
349                 // class in the closure environment
350                 ReadOnlyCollection<Func<bool>> recompileRequiredDelegates = _recompileRequiredDelegates.AsReadOnly();
351                 return () => recompileRequiredDelegates.Any(d => d());
352             }
353 
Visit(Expression exp)354             internal override Expression Visit(Expression exp)
355             {
356                 if (exp != null)
357                 {
358                     if (!_funcletizer._linqExpressionStack.Add(exp))
359                     {
360                         // This expression is already in the stack.
361                         throw EntityUtil.InvalidOperation(Strings.ELinq_CycleDetected);
362                     }
363 
364                     try
365                     {
366                         if (_isClientConstant(exp))
367                         {
368                             return InlineValue(exp, false);
369                         }
370                         else if (_isClientVariable(exp))
371                         {
372                             TypeUsage queryParameterType;
373                             if (_funcletizer.TryGetTypeUsageForTerminal(exp.Type, out queryParameterType))
374                             {
375                                 DbParameterReferenceExpression parameterReference = queryParameterType.Parameter(_funcletizer.GenerateParameterName());
376                                 return new QueryParameterExpression(parameterReference, exp, _funcletizer._compiledQueryParameters);
377                             }
378                             else if (_funcletizer.IsCompiledQuery)
379                             {
380                                 throw InvalidCompiledQueryParameterException(exp);
381                             }
382                             else
383                             {
384                                 return InlineValue(exp, true);
385                             }
386                         }
387                         return base.Visit(exp);
388                     }
389                     finally
390                     {
391                         _funcletizer._linqExpressionStack.Remove(exp);
392                     }
393                 }
394                 return base.Visit(exp);
395             }
396 
InvalidCompiledQueryParameterException(Expression expression)397             private static NotSupportedException InvalidCompiledQueryParameterException(Expression expression)
398             {
399                 ParameterExpression parameterExp;
400                 if (expression.NodeType == ExpressionType.Parameter)
401                 {
402                     parameterExp = (ParameterExpression)expression;
403                 }
404                 else
405                 {
406                     // If this is a simple query parameter (involving a single delegate parameter) report the
407                     // type of that parameter. Otherwise, report the type of the part of the parameter.
408                     HashSet<ParameterExpression> parameters = new HashSet<ParameterExpression>();
409                     EntityExpressionVisitor.Visit(expression, (exp, baseVisit) =>
410                     {
411                         if (null != exp && exp.NodeType == ExpressionType.Parameter)
412                         {
413                             parameters.Add((ParameterExpression)exp);
414                         }
415                         return baseVisit(exp);
416                     });
417 
418                     if (parameters.Count != 1)
419                     {
420                         return EntityUtil.NotSupported(Strings.CompiledELinq_UnsupportedParameterTypes(expression.Type.FullName));
421                     }
422 
423                     parameterExp = parameters.Single();
424                 }
425 
426                 if (parameterExp.Type.Equals(expression.Type))
427                 {
428                     // If the expression type is the same as the parameter type, indicate that the parameter type is not valid.
429                     return EntityUtil.NotSupported(Strings.CompiledELinq_UnsupportedNamedParameterType(parameterExp.Name, parameterExp.Type.FullName));
430                 }
431                 else
432                 {
433                     // Otherwise, indicate that using the specified parameter to produce a value of the expression's type is not supported in compiled query
434                     return EntityUtil.NotSupported(Strings.CompiledELinq_UnsupportedNamedParameterUseAsType(parameterExp.Name, expression.Type.FullName));
435                 }
436             }
437 
438             /// <summary>
439             /// Compiles a delegate returning the value of the given expression.
440             /// </summary>
CompileExpression(Expression expression)441             private Func<object> CompileExpression(Expression expression)
442             {
443                 Func<object> func = Expression
444                     .Lambda<Func<object>>(TypeSystem.EnsureType(expression, typeof(object)))
445                     .Compile();
446                 return func;
447             }
448 
449             /// <summary>
450             /// Inlines a funcletizable expression. Queries and lambda expressions are expanded
451             /// inline. All other values become simple constants.
452             /// </summary>
InlineValue(Expression expression, bool recompileOnChange)453             private Expression InlineValue(Expression expression, bool recompileOnChange)
454             {
455                 Func<object> getValue = null;
456                 object value = null;
457                 if (expression.NodeType == ExpressionType.Constant)
458                 {
459                     value = ((ConstantExpression)expression).Value;
460                 }
461                 else
462                 {
463                     bool fastPath = false;
464                     //fastpath to process object query
465                     if (expression.NodeType == ExpressionType.Convert)
466                     {
467                         var ue = (UnaryExpression)expression;
468                         // The ObjectSet instance is wrapped inside Convert UnaryExpression in
469                         // ElinqQueryState.GetExpression(). The block below identifies such an
470                         // expression, makes sure the object query it contains is immutable and
471                         // extracts the reference to the object query.
472                         if (!recompileOnChange
473                             && ue.Operand.NodeType == ExpressionType.Constant
474                             && typeof(IQueryable).IsAssignableFrom(ue.Operand.Type))
475                         {
476                             value = ((ConstantExpression)ue.Operand).Value;
477                             fastPath = true;
478                         }
479                     }
480                     if (!fastPath)
481                     {
482                         getValue = CompileExpression(expression);
483                         value = getValue();
484                     }
485                 }
486 
487                 Expression result = null;
488                 ObjectQuery inlineQuery = value as ObjectQuery;
489                 if (inlineQuery != null)
490                 {
491                     result = InlineObjectQuery(inlineQuery, expression.Type);
492                 }
493                 else
494                 {
495                     LambdaExpression lambda = value as LambdaExpression;
496                     if (null != lambda)
497                     {
498                         result = InlineExpression(Expression.Quote(lambda));
499                     }
500                     else
501                     {
502                         // everything else is just a constant...
503                         result = expression.NodeType == ExpressionType.Constant
504                             ? expression
505                             : Expression.Constant(value, expression.Type);
506                     }
507                 }
508 
509                 if (recompileOnChange)
510                 {
511                     AddRecompileRequiredDelegates(getValue, value);
512                 }
513 
514                 return result;
515             }
516 
AddRecompileRequiredDelegates(Func<object> getValue, object value)517             private void AddRecompileRequiredDelegates(Func<object> getValue, object value)
518             {
519                 // Build a delegate that returns true when the inline value has changed.
520                 // Outside of ObjectQuery, this amounts to a reference comparison.
521                 ObjectQuery originalQuery = value as ObjectQuery;
522                 if (null != originalQuery)
523                 {
524                     // For inline queries, we need to check merge options as well (it's mutable)
525                     MergeOption? originalMergeOption = originalQuery.QueryState.UserSpecifiedMergeOption;
526                     if (null == getValue)
527                     {
528                         _recompileRequiredDelegates.Add(() => originalQuery.QueryState.UserSpecifiedMergeOption != originalMergeOption);
529                     }
530                     else
531                     {
532                         _recompileRequiredDelegates.Add(() =>
533                         {
534                             ObjectQuery currentQuery = getValue() as ObjectQuery;
535                             return !object.ReferenceEquals(originalQuery, currentQuery) ||
536                                 currentQuery.QueryState.UserSpecifiedMergeOption != originalMergeOption;
537                         });
538 
539                     }
540                 }
541                 else if (null != getValue)
542                 {
543                     _recompileRequiredDelegates.Add(() => !object.ReferenceEquals(value, getValue()));
544                 }
545             }
546 
547             /// <summary>
548             /// Gets the appropriate LINQ expression for an inline ObjectQuery instance.
549             /// </summary>
InlineObjectQuery(ObjectQuery inlineQuery, Type expressionType)550             private Expression InlineObjectQuery(ObjectQuery inlineQuery, Type expressionType)
551             {
552                 EntityUtil.CheckArgumentNull(inlineQuery, "inlineQuery");
553 
554                 Expression queryExpression;
555                 if (_funcletizer._mode == Mode.CompiledQueryLockdown)
556                 {
557                     // In the lockdown phase, we don't chase down inline object queries because
558                     // we don't yet know what the object context is supposed to be.
559                     queryExpression = Expression.Constant(inlineQuery, expressionType);
560                 }
561                 else
562                 {
563                     if (!object.ReferenceEquals(_funcletizer._rootContext, inlineQuery.QueryState.ObjectContext))
564                     {
565                         throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedDifferentContexts);
566                     }
567 
568                     queryExpression = inlineQuery.GetExpression();
569 
570                     // If it's not an entity-sql (terminal) query, recursively process
571                     if (!(inlineQuery.QueryState is EntitySqlQueryState))
572                     {
573                         queryExpression = InlineExpression(queryExpression);
574                     }
575 
576                     queryExpression = TypeSystem.EnsureType(queryExpression, expressionType);
577                 }
578 
579                 return queryExpression;
580             }
581 
InlineExpression(Expression exp)582             private Expression InlineExpression(Expression exp)
583             {
584                 Func<bool> inlineExpressionRequiresRecompile;
585                 exp = _funcletizer.Funcletize(exp, out inlineExpressionRequiresRecompile);
586                 if (!_funcletizer.IsCompiledQuery)
587                 {
588                     _recompileRequiredDelegates.Add(inlineExpressionRequiresRecompile);
589                 }
590                 return exp;
591             }
592         }
593     }
594 
595     /// <summary>
596     /// A LINQ expression corresponding to a query parameter.
597     /// </summary>
598     internal sealed class QueryParameterExpression : Expression
599     {
600         private readonly DbParameterReferenceExpression _parameterReference;
601         private readonly Type _type;
602         private readonly Expression _funcletizedExpression;
603         private readonly IEnumerable<ParameterExpression> _compiledQueryParameters;
604         private Delegate _cachedDelegate;
605 
QueryParameterExpression( DbParameterReferenceExpression parameterReference, Expression funcletizedExpression, IEnumerable<ParameterExpression> compiledQueryParameters)606         internal QueryParameterExpression(
607             DbParameterReferenceExpression parameterReference,
608             Expression funcletizedExpression,
609             IEnumerable<ParameterExpression> compiledQueryParameters)
610         {
611             EntityUtil.CheckArgumentNull(parameterReference, "parameterReference");
612             EntityUtil.CheckArgumentNull(funcletizedExpression, "funcletizedExpression");
613             _compiledQueryParameters = compiledQueryParameters ?? Enumerable.Empty<ParameterExpression>();
614             _parameterReference = parameterReference;
615             _type = funcletizedExpression.Type;
616             _funcletizedExpression = funcletizedExpression;
617             _cachedDelegate = null;
618         }
619 
620         /// <summary>
621         /// Gets the current value of the parameter given (optional) compiled query arguments.
622         /// </summary>
EvaluateParameter(object[] arguments)623         internal object EvaluateParameter(object[] arguments)
624         {
625             if (_cachedDelegate == null)
626             {
627                 if (_funcletizedExpression.NodeType == ExpressionType.Constant)
628                 {
629                     return ((ConstantExpression)_funcletizedExpression).Value;
630                 }
631                 ConstantExpression ce;
632                 if (TryEvaluatePath(_funcletizedExpression, out ce))
633                 {
634                     return ce.Value;
635                 }
636             }
637 
638             try
639             {
640                 if (_cachedDelegate == null)
641                 {
642                     // Get the Func<> type for the property evaluator
643                     Type delegateType = TypeSystem.GetDelegateType(_compiledQueryParameters.Select(p => p.Type), _type);
644 
645                     // Now compile delegate for the funcletized expression
646                     _cachedDelegate = Expression.Lambda(delegateType, _funcletizedExpression, _compiledQueryParameters).Compile();
647                 }
648                 return _cachedDelegate.DynamicInvoke(arguments);
649             }
650             catch (TargetInvocationException e)
651             {
652                 throw e.InnerException;
653             }
654         }
655 
656         /// <summary>
657         /// Create QueryParameterExpression based on this one, but with the funcletized expression
658         /// wrapped by the given method
659         /// </summary>
660         /// <param name="method"></param>
661         /// <returns></returns>
EscapeParameterForLike(Func<string, string> method)662         internal QueryParameterExpression EscapeParameterForLike(Func<string, string> method)
663         {
664             Expression wrappedExpression = Expression.Invoke(Expression.Constant(method), this._funcletizedExpression);
665             return new QueryParameterExpression(this._parameterReference, wrappedExpression, this._compiledQueryParameters);
666         }
667 
668         /// <summary>
669         /// Gets the parameter reference for the parameter.
670         /// </summary>
671         internal DbParameterReferenceExpression ParameterReference
672         {
673             get { return _parameterReference; }
674         }
675 
676         public override Type Type
677         {
678             get { return _type; }
679         }
680 
681         public override ExpressionType NodeType
682         {
683             get { return EntityExpressionVisitor.CustomExpression; }
684         }
685 
TryEvaluatePath(Expression expression, out ConstantExpression constantExpression)686         private bool TryEvaluatePath(Expression expression, out ConstantExpression constantExpression)
687         {
688             MemberExpression me = expression as MemberExpression;
689             constantExpression = null;
690             if (me != null)
691             {
692                 Stack<MemberExpression> stack = new Stack<MemberExpression>();
693                 stack.Push(me);
694                 while ((me = me.Expression as MemberExpression) != null)
695                 {
696                     stack.Push(me);
697                 }
698                 me = stack.Pop();
699                 ConstantExpression ce = me.Expression as ConstantExpression;
700                 if (ce != null)
701                 {
702                     object memberVal;
703                     if (!TryGetFieldOrPropertyValue(me, ((ConstantExpression)me.Expression).Value, out memberVal))
704                     {
705                         return false;
706                     }
707                     if (stack.Count > 0)
708                     {
709                         foreach (var rec in stack)
710                         {
711                             if (!TryGetFieldOrPropertyValue(rec, memberVal, out memberVal))
712                             {
713                                 return false;
714                             }
715                         }
716                     }
717                     constantExpression = Expression.Constant(memberVal, expression.Type);
718                     return true;
719                 }
720             }
721             return false;
722         }
723 
TryGetFieldOrPropertyValue(MemberExpression me, object instance, out object memberValue)724         private bool TryGetFieldOrPropertyValue(MemberExpression me, object instance, out object memberValue)
725         {
726             bool result = false;
727             memberValue = null;
728 
729             try
730             {
731                 if (me.Member.MemberType == MemberTypes.Field)
732                 {
733                     memberValue = ((FieldInfo)me.Member).GetValue(instance);
734                     result = true;
735                 }
736                 else if (me.Member.MemberType == MemberTypes.Property)
737                 {
738                     memberValue = ((PropertyInfo)me.Member).GetValue(instance, null);
739                     result = true;
740                 }
741                 return result;
742             }
743             catch (TargetInvocationException ex)
744             {
745                 throw ex.InnerException;
746             }
747         }
748     }
749 }
750