/// <summary> /// Serializes an expression to a string /// </summary> /// <param name='context'>Data context used to generate type names for types.</param> /// <param name="e">Expression to serialize</param> /// <param name='inPath'>Whether or not the expression being written is part of the path of the URI.</param> /// <param name="uriVersion">the request data service version for the uri</param> /// <returns>serialized expression</returns> internal static string ExpressionToString(DataServiceContext context, Expression e, bool inPath, ref Version uriVersion) { ExpressionWriter ew = new ExpressionWriter(context, inPath); string serialized = ew.Translate(e); WebUtil.RaiseVersion(ref uriVersion, ew.uriVersion); if (ew.cantTranslateExpression) { throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(e.ToString())); } return(serialized); }
/// <summary> /// Input resource set references are intentionally omitted from the URL string for the top level /// references to input parameter (i.e. outside of any/all methods). /// For parameter references to input (range variable for Where) inside any/all methods we write "$it". /// </summary> /// <param name="ire">The input reference</param> /// <returns>The same input reference expression</returns> internal override Expression VisitInputReferenceExpression(InputReferenceExpression ire) { // This method intentionally does not write anything to the URI for implicit references to the input parameter ($it). // This is how 'Where(<input>.Id == 5)' becomes '$filter=Id eq 5'. Debug.Assert(ire != null, "ire != null"); if (this.parent == null || (!this.InSubScope && this.parent.NodeType != ExpressionType.MemberAccess && this.parent.NodeType != ExpressionType.TypeAs && this.parent.NodeType != ExpressionType.Call)) { // Ideally we refer to the parent expression as the un-translatable one, // because we cannot reference 'this' as a standalone expression; however // if the parent is null for any reason, we fall back to the expression itself. string expressionText = (this.parent != null) ? this.parent.ToString() : ire.ToString(); throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(expressionText)); } // Write "$it" for input parameter reference inside any/all methods if (this.InSubScope || this.parent.NodeType == ExpressionType.Call) { this.builder.Append(XmlConstants.ImplicitFilterParameter); } return(ire); }
/// <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, 'contains', 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 == "contains") { 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.Object); this.builder.Append(UriHelper.COMMA); this.Visit(m.Arguments[0]); } 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 if (m.Method.Name == "HasFlag") { Debug.Assert(m.Method.Name == "HasFlag", "m.Method.Name == 'HasFlag'"); Debug.Assert(m.Object != null, "m.Object != null"); Debug.Assert(m.Arguments.Count == 1, "m.Arguments.Count == 1"); this.Visit(m.Object); this.builder.Append(UriHelper.SPACE); this.builder.Append(UriHelper.HAS); this.builder.Append(UriHelper.SPACE); this.Visit(m.Arguments[0]); } 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.ODataVersion4); 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); } } } else if (sequenceMethod == SequenceMethod.Count && this.parent != null) { if (m.Arguments.Any() && m.Arguments[0] != null) { this.Visit(m.Arguments[0]); } this.builder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); return(m); } else if (sequenceMethod == SequenceMethod.Contains) { // First argument is the collection expression // Second argument is the value expression // Note that arguments must be reordered for the IN operator // e.g. ctx.CreateQuery<Product>("Products").Where(p => (new [] { "Milk", "Cheese", "Donut"}).Contains(p.Name)) // which translates to /Products()?$filter=Name in ('Milk', 'Cheese', 'Donut') this.Visit(m.Arguments[1]); this.builder.Append(UriHelper.SPACE) .Append(UriHelper.IN) .Append(UriHelper.SPACE); this.Visit(m.Arguments[0]); return(m); } } else { if (m.Object != null) { this.Visit(m.Object); } if (m.Method.Name != "GetValue" && m.Method.Name != "GetValueAsync") { this.builder.Append(UriHelper.FORWARDSLASH); // writing functions in query options writingFunctionsInQuery = true; string declaringType = this.context.ResolveNameFromTypeInternal(m.Method.DeclaringType); if (string.IsNullOrEmpty(declaringType)) { throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(m.ToString())); } int index = declaringType.LastIndexOf('.'); string fullNamespace = declaringType.Remove(index + 1); string serverMethodName = ClientTypeUtil.GetServerDefinedName(m.Method); this.builder.Append(fullNamespace + serverMethodName); this.builder.Append(UriHelper.LEFTPAREN); string[] argumentNames = m.Method.GetParameters().Select(p => p.Name).ToArray(); for (int i = 0; i < m.Arguments.Count; ++i) { this.builder.Append(argumentNames[i]); this.builder.Append(UriHelper.EQUALSSIGN); this.scopeCount++; this.Visit(m.Arguments[i]); this.scopeCount--; this.builder.Append(UriHelper.COMMA); } if (m.Arguments.Any()) { this.builder.Remove(this.builder.Length - 1, 1); } this.builder.Append(UriHelper.RIGHTPAREN); writingFunctionsInQuery = false; } return(m); } this.cantTranslateExpression = true; } return(m); }