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