/// <summary> /// Looks for a method call expression of the form /// Select(entity => Convert(entity, DerivedType)) /// If found, returns DerivedType. /// </summary> /// <param name="callExpression">Expression to check for pattern match.</param> /// <param name="convertType">If the match was found, this is the type used in the Convert, otherwise null.</param> /// <returns>True if the expression matches the desired pattern, otherwise false.</returns> private static bool TryMatchSelectWithConvert(MethodCallExpression callExpression, out Type convertType) { convertType = null; return(ReflectionUtil.IsSequenceMethod(callExpression.Method, SequenceMethod.Select) && TryMatchConvertSingleArgument(callExpression.Arguments[1], out convertType)); }
internal override Expression VisitMethodCall(MethodCallExpression m) { string str; SequenceMethod method; if (TypeSystem.TryGetQueryOptionMethod(m.Method, out str)) { this.builder.Append(str); this.builder.Append('('); if (str == "substringof") { this.Visit(m.Arguments[0]); this.builder.Append(','); this.Visit(m.Object); } else { if (m.Object != null) { this.Visit(m.Object); } if (m.Arguments.Count > 0) { if (m.Object != null) { this.builder.Append(','); } for (int i = 0; i < m.Arguments.Count; i++) { this.Visit(m.Arguments[i]); if (i < (m.Arguments.Count - 1)) { this.builder.Append(','); } } } } this.builder.Append(')'); return(m); } if (ReflectionUtil.TryIdentifySequenceMethod(m.Method, out method)) { if (ReflectionUtil.IsAnyAllMethod(method)) { WebUtil.RaiseVersion(ref this.uriVersion, Util.DataServiceVersion3); this.Visit(m.Arguments[0]); this.builder.Append('/'); if (method == SequenceMethod.All) { this.builder.Append("all"); } else { this.builder.Append("any"); } this.builder.Append('('); if (method != SequenceMethod.Any) { LambdaExpression expression = (LambdaExpression)m.Arguments[1]; string name = expression.Parameters[0].Name; this.builder.Append(name); this.builder.Append(':'); this.scopeCount++; this.Visit(expression.Body); this.scopeCount--; } this.builder.Append(')'); return(m); } if ((method == SequenceMethod.OfType) && (this.parent != null)) { MethodCallExpression parent = this.parent as MethodCallExpression; if (((parent != null) && ReflectionUtil.TryIdentifySequenceMethod(parent.Method, out method)) && ReflectionUtil.IsAnyAllMethod(method)) { Type type = parent.Method.GetGenericArguments().SingleOrDefault <Type>(); if (ClientTypeUtil.TypeOrElementTypeIsEntity(type)) { this.Visit(m.Arguments[0]); this.builder.Append('/'); this.builder.Append(System.Data.Services.Client.UriHelper.GetEntityTypeNameForUriAndValidateMaxProtocolVersion(type, this.context, ref this.uriVersion)); return(m); } } } } this.cantTranslateExpression = true; return(m); }
/// <summary> /// Identifies and normalizes a Select method call expression of the following form: /// Select(x => Convert(x)) /// If a match is found, it is translated into a Cast() call. /// /// This supports type casting in queries like the following: /// from DerivedType entity in context.Entities /// select entity /// Where DerivedType is derived from the type of context.Entities. /// The pattern also applies to SelectMany calls with the same structure as above. /// /// In C#, the type cast above is represented as a Cast call and the ResourceBinder knows how to handle that. /// In VB, the same query is translated into Select(x => Convert(x)) instead of Cast, and the ResourceBinder /// doesn't recognize that pattern. This normalization allows the two queries to be treated the same. /// </summary> /// <param name="callExpression">MethodCallExpression to potentially normalize.</param> /// <returns> /// If the query pattern was found, a Cast call is returned with the same source as the original Select and /// a cast type that is the same as the original Convert expression. /// If no normalization is required, the original MethodCallExpression is returned without changes. /// </returns> private static MethodCallExpression NormalizeSelectWithTypeCast(MethodCallExpression callExpression) { Type convertType; if (TryMatchSelectWithConvert(callExpression, out convertType)) { // Find the Cast method on the same type where the Select method was declared MethodInfo castMethodInfo = callExpression.Method.DeclaringType.GetMethod("Cast", true /*isPublic*/, true /*isStatic*/); if (castMethodInfo != null && castMethodInfo.IsGenericMethodDefinition && ReflectionUtil.IsSequenceMethod(castMethodInfo, SequenceMethod.Cast)) { MethodInfo genericCastMethodInfo = castMethodInfo.MakeGenericMethod(convertType); return(Expression.Call(genericCastMethodInfo, callExpression.Arguments[0])); } } // nothing has changed return(callExpression); }
/// <summary> /// MethodCallExpression visit method /// </summary> /// <param name="m">The MethodCallExpression expression to visit</param> /// <returns>The visited MethodCallExpression expression </returns> internal override Expression VisitMethodCall(MethodCallExpression m) { string methodName; if (TypeSystem.TryGetQueryOptionMethod(m.Method, out methodName)) { this.builder.Append(methodName); this.builder.Append(UriHelper.LEFTPAREN); // There is a single function, 'substringof', which reorders its argument with // respect to the CLR method. Thus handling it as a special case rather than // using a more general argument reordering mechanism. if (methodName == "substringof") { Debug.Assert(m.Method.Name == "Contains", "m.Method.Name == 'Contains'"); Debug.Assert(m.Object != null, "m.Object != null"); Debug.Assert(m.Arguments.Count == 1, "m.Arguments.Count == 1"); this.Visit(m.Arguments[0]); this.builder.Append(UriHelper.COMMA); this.Visit(m.Object); } else { if (m.Object != null) { this.Visit(m.Object); } if (m.Arguments.Count > 0) { if (m.Object != null) { this.builder.Append(UriHelper.COMMA); } for (int ii = 0; ii < m.Arguments.Count; ii++) { this.Visit(m.Arguments[ii]); if (ii < m.Arguments.Count - 1) { this.builder.Append(UriHelper.COMMA); } } } } this.builder.Append(UriHelper.RIGHTPAREN); } else { SequenceMethod sequenceMethod; if (ReflectionUtil.TryIdentifySequenceMethod(m.Method, out sequenceMethod)) { if (ReflectionUtil.IsAnyAllMethod(sequenceMethod)) { // Raise the uriVersion each time we write any or all methods to the uri. WebUtil.RaiseVersion(ref this.uriVersion, Util.DataServiceVersion3); this.Visit(m.Arguments[0]); this.builder.Append(UriHelper.FORWARDSLASH); if (sequenceMethod == SequenceMethod.All) { this.builder.Append(XmlConstants.AllMethodName); } else { this.builder.Append(XmlConstants.AnyMethodName); } this.builder.Append(UriHelper.LEFTPAREN); if (sequenceMethod != SequenceMethod.Any) { // SequenceMethod.Any represents Enumerable.Any(), which has only source argument // AnyPredicate and All has a second parameter which is the predicate lambda. Debug.Assert(m.Arguments.Count() == 2, "m.Arguments.Count() == 2"); LambdaExpression le = (LambdaExpression)m.Arguments[1]; string rangeVariable = le.Parameters[0].Name; this.builder.Append(rangeVariable); this.builder.Append(UriHelper.COLON); this.scopeCount++; this.Visit(le.Body); this.scopeCount--; } this.builder.Append(UriHelper.RIGHTPAREN); return(m); } else if (sequenceMethod == SequenceMethod.OfType && this.parent != null) { // check to see if this is an OfType filter for Any or All. // e.g. ctx.CreateQuery<Movie>("Movies").Where(m=>m.Actors.OfType<MegaStar>().Any()) // which translates to /Movies()?$filter=Actors/MegaStar/any() MethodCallExpression mce = this.parent as MethodCallExpression; if (mce != null && ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod) && ReflectionUtil.IsAnyAllMethod(sequenceMethod)) { Type filteredType = mce.Method.GetGenericArguments().SingleOrDefault(); if (ClientTypeUtil.TypeOrElementTypeIsEntity(filteredType)) { this.Visit(m.Arguments[0]); this.builder.Append(UriHelper.FORWARDSLASH); UriHelper.AppendTypeSegment(this.builder, filteredType, this.context, this.inPath, ref this.uriVersion); return(m); } } } } this.cantTranslateExpression = true; } return(m); }