protected override void VisitConvert(ODataConvertExpression node) { var convertExpression = node.Expression.Type.IsImplicityCastableTo(node.Type) ? node.Expression : ODataExpression.Call(ODataFunction.Cast, new[] { node.Expression, ODataExpression.Constant(node.ClrType) }); this.Visit(convertExpression); }
/// <summary> /// Attempts to translate the given <see cref="MemberExpression"/> as a special OData member, like <see cref="Nullable{T}.Value"/> or /// <see cref="string.Length"/>. /// </summary> private bool TryTranslateMemberAccessAsSpecialMember(MemberExpression memberAccess, out ODataExpression result) { // TODO FUTURE null handling? Func <IEnumerable <ODataExpression> > translateInstance = () => this._translator.TranslateInternal(memberAccess.Expression).Enumerate(); if (memberAccess.Member.DeclaringType == typeof(string) && memberAccess.Member.Name == "Length") { result = ODataExpression.Call(ODataFunction.Length, translateInstance()); } else if (memberAccess.Member.DeclaringType == typeof(DateTime) && memberAccess.Member.Name == "Year") { result = ODataExpression.Call(ODataFunction.Year, translateInstance()); } else if (memberAccess.Member.DeclaringType == typeof(DateTime) && memberAccess.Member.Name == "Month") { result = ODataExpression.Call(ODataFunction.Month, translateInstance()); } else if (memberAccess.Member.DeclaringType == typeof(DateTime) && memberAccess.Member.Name == "Day") { result = ODataExpression.Call(ODataFunction.Day, translateInstance()); } else if (memberAccess.Member.DeclaringType == typeof(DateTime) && memberAccess.Member.Name == "Hour") { result = ODataExpression.Call(ODataFunction.Hour, translateInstance()); } else if (memberAccess.Member.DeclaringType == typeof(DateTime) && memberAccess.Member.Name == "Minute") { result = ODataExpression.Call(ODataFunction.Minute, translateInstance()); } else if (memberAccess.Member.DeclaringType == typeof(DateTime) && memberAccess.Member.Name == "Second") { result = ODataExpression.Call(ODataFunction.Second, translateInstance()); } // nullable properties else if (memberAccess.Member.Name == "HasValue" && memberAccess.Member.DeclaringType.IsGenericOfType(typeof(Nullable <>))) { // for HasValue we just re-translate expr != null result = this._translator.TranslateInternal(Expression.NotEqual(memberAccess.Expression, Expression.Constant(null, memberAccess.Expression.Type))); } else if (memberAccess.Member.Name == "Value" && memberAccess.Member.DeclaringType.IsGenericOfType(typeof(Nullable <>))) { // .Value calls can just be ignored, since OData doesn't have the notion of nullable types result = this._translator.TranslateInternal(memberAccess.Expression); } else { result = null; return(false); } return(true); }
private ODataExpression ParseSimple() { if (this.TryEat(ODataTokenKind.LeftParen)) { // parse group var group = this.ParseExpression(); this.Eat(ODataTokenKind.RightParen); return(group); } ODataToken next; if (this.TryEat(ODataTokenKind.Identifier, out next)) { if (this.TryEat(ODataTokenKind.LeftParen)) { // parse function ODataFunction function; if (!Enum.TryParse(next.Text, ignoreCase: true, result: out function)) { throw new ODataParseException(next.Text + " is not a known ODataFunction!"); } var arguments = this.ParseExpressionList(this.ParseExpression, ODataTokenKind.Comma); this.Eat(ODataTokenKind.RightParen); if (function == ODataFunction.IsOf || function == ODataFunction.Cast) { var typeLiteral = this.ReParseAsType(arguments[arguments.Count - 1]); var argumentsCopy = arguments.ToArray(); argumentsCopy[arguments.Count - 1] = ODataExpression.Constant(typeLiteral); arguments = argumentsCopy; } return(ODataExpression.Call(function, arguments)); } // parse member access var type = this._elementType; // root element type ODataMemberAccessExpression access = null; // root member while (true) { // get the property var property = typeof(ODataObject).IsAssignableFrom(type) ? ODataEntity.GetProperty(next.Text, typeof(ODataObject)) : type.GetProperty(next.Text, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (property == null) { throw new ODataParseException("Property '" + next.Text + "' could not be found on type " + type.FullName); } access = ODataExpression.MemberAccess(access, property); // if we don't see '/' followed by an identifier, we're done // we can't just use TryEat() here, because we need to distinguish between Foo/Bar/* and Foo/Bar/Baz if (this.Next().Kind != ODataTokenKind.Slash || this.Next(2).Kind != ODataTokenKind.Identifier) { break; } // otherwise, update next to the next id and then advance type down the property chain this.Eat(ODataTokenKind.Slash); next = this.Eat(ODataTokenKind.Identifier); type = property.PropertyType; } return(access); } // literals if (this.TryEat(ODataTokenKind.NullLiteral, out next)) { return(ODataExpression.Constant(null)); } if (this.TryEat(ODataTokenKind.BinaryLiteral, out next)) { throw new NotImplementedException("Binary Literal Parse"); } if (this.TryEat(ODataTokenKind.BooleanLiteral, out next)) { return(ODataExpression.Constant(bool.Parse(next.Text))); } // see comment on the enum //if (this.TryEat(ODataTokenKind.ByteLiteral, out next)) //{ // return ODataExpression.Constant(Convert.ToByte(next.Text, fromBase: 16)); //} if (this.TryEat(ODataTokenKind.DateTimeLiteral, out next)) { Func <string, string> zeroIfEmpty = s => s.Length == 0 ? "0" : s; var dateTime = new DateTime( year: int.Parse(next.Match.Groups["year"].Value), month: int.Parse(next.Match.Groups["month"].Value), day: int.Parse(next.Match.Groups["day"].Value), hour: int.Parse(next.Match.Groups["hour"].Value), minute: int.Parse(next.Match.Groups["minute"].Value), second: int.Parse(zeroIfEmpty(next.Match.Groups["second"].Value)), millisecond: 0 ) .AddSeconds(double.Parse(zeroIfEmpty(next.Match.Groups["fraction"].Value))); return(ODataExpression.Constant(dateTime)); } if (this.TryEat(ODataTokenKind.DecimalLiteral, out next)) { return(ODataExpression.Constant(decimal.Parse(next.Text.Substring(0, next.Text.Length - 1)))); } if (this.TryEat(ODataTokenKind.DoubleLiteral, out next)) { return(ODataExpression.Constant(double.Parse(next.Text))); } if (this.TryEat(ODataTokenKind.SingleLiteral, out next)) { return(ODataExpression.Constant(float.Parse(next.Text.Substring(0, next.Text.Length - 1)))); } if (this.TryEat(ODataTokenKind.GuidLiteral, out next)) { return(ODataExpression.Constant(Guid.Parse(next.Match.Groups["digits"].Value))); } if (this.TryEat(ODataTokenKind.Int32Literal, out next)) { // Note: this will fail hard if we have an out-of-range int value. However, this is consistent // with MSFT's implementation (see http://services.odata.org/v3/odata/odata.svc/Products?$format=json&$filter=Price%20ge%202147483648) return(ODataExpression.Constant(int.Parse(next.Text))); } if (this.TryEat(ODataTokenKind.Int64Literal, out next)) { return(ODataExpression.Constant(long.Parse(next.Text.Substring(0, next.Text.Length - 1)))); } if (this.TryEat(ODataTokenKind.StringLiteral, out next)) { // unescaping, from http://stackoverflow.com/questions/3979367/how-to-escape-a-single-quote-to-be-used-in-an-odata-query return(ODataExpression.Constant(next.Match.Groups["chars"].Value.Replace("''", "'"))); } throw new ODataParseException("Unexpected token " + this.Next()); }
protected override void VisitCall(ODataCallExpression node) { // reference for different function syntax http://users.atw.hu/sqlnut/sqlnut2-chp-4-sect-4.html switch (node.Function) { case ODataFunction.Cast: this.Write("CAST(").Write(node.Arguments[0]); var toType = ((Type)((ODataConstantExpression)node.Arguments[1]).Value).ToODataExpressionType(); this.Write(" AS ").Write(this.syntaxProvider.GetSqlTypeName(toType)).Write(")"); break; case ODataFunction.Ceiling: this.VisitCallHelper(this.syntaxProvider.UseAbbreviatedCeilingFunction ? "CEIL" : "CEILING", node); break; case ODataFunction.Concat: this.Write("(").Write(node.Arguments[0]) .Write(" + ") .Write(node.Arguments[1]).Write(")"); break; case ODataFunction.Second: case ODataFunction.Minute: case ODataFunction.Hour: case ODataFunction.Day: case ODataFunction.Month: case ODataFunction.Year: this.syntaxProvider.RenderDatePartFunctionCall(node.Function, s => this.Write(s), () => this.Write(node.Arguments[0])); break; case ODataFunction.EndsWith: // endswith => (INDEXOF(needle, haystack) = LEN(haystack) - LEN(needle)) OR LEN(needle) = 0 var needleLengthExpression = ODataExpression.Call(ODataFunction.Length, new[] { node.Arguments[1] }); var endsWithExpression = ODataExpression.BinaryOp( ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.IndexOf, node.Arguments), ODataBinaryOp.Equal, ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.Length, new[] { node.Arguments[0] }), ODataBinaryOp.Subtract, needleLengthExpression ) ), ODataBinaryOp.Or, ODataExpression.BinaryOp( needleLengthExpression, ODataBinaryOp.Equal, ODataExpression.Constant(0) ) ); this.Visit(endsWithExpression); break; case ODataFunction.StartsWith: // startswith => INDEXOF(needle, haystack) = 0 var startsWithExpression = ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.IndexOf, node.Arguments), ODataBinaryOp.Equal, ODataExpression.Constant(0) ); this.Visit(startsWithExpression); break; case ODataFunction.SubstringOf: // substringof => INDEXOF(needle, haystack) >= 0 var substringOfExpression = ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.IndexOf, node.Arguments.Reverse()), ODataBinaryOp.GreaterThanOrEqual, ODataExpression.Constant(0) ); this.Visit(substringOfExpression); break; case ODataFunction.IndexOf: this.syntaxProvider.RenderIndexOfFunctionCall(s => this.Write(s), renderNeedleArgument: () => this.Write(node.Arguments[1]), renderHaystackArgument: () => this.Write(node.Arguments[0])); break; case ODataFunction.Floor: this.VisitCallHelper("FLOOR", node); break; case ODataFunction.Length: this.VisitCallHelper(this.syntaxProvider.StringLengthFunctionName, node); break; case ODataFunction.Replace: this.VisitCallHelper("REPLACE", node); break; case ODataFunction.Round: this.syntaxProvider.RenderRoundFunctionCall(s => this.Write(s), () => this.Write(node.Arguments[0])); break; case ODataFunction.Substring: this.syntaxProvider.RenderSubstringFunctionCall(s => this.Write(s), () => this.Write(node.Arguments[0]), () => this.Write(node.Arguments[1]), node.Arguments.Count > 2 ? new Action(() => this.Write(node.Arguments[2])) : null); break; case ODataFunction.ToLower: this.VisitCallHelper("LOWER", node); break; case ODataFunction.ToUpper: this.VisitCallHelper("UPPER", node); break; case ODataFunction.Trim: if (this.syntaxProvider.HasTwoSidedTrim) { this.VisitCallHelper("TRIM", node); } else { // call both LTRIM and RTRIM this.VisitCallHelper("LTRIM(RTRIM", node); this.Write(")"); } break; case ODataFunction.IsOf: throw new NotSupportedException(node.Function.ToString()); } }
private ODataExpression TranslateInternal(Expression linq) { if (linq == null) { return(null); } // captured parameters object value; if (TryGetValueFast(linq, out value)) { // special case the handling of queryable constants, since these can be the "root" // of the query expression tree. We check for isInsideQuery since we don't allow multiple // roots. An example of a multiply-rooted tree would be: q.Where(x => q.Any(xx => xx.A < x.A)) if (!this._isInsideQuery && typeof(IQueryable).GetTypeInfo().IsAssignableFrom(linq.Type.GetTypeInfo())) { this._rootQuery = (IQueryable)value; return(ODataExpression.Query()); } return(ODataExpression.Constant(value, linq.Type)); } switch (linq.NodeType) { case ExpressionType.OrElse: return(this.TranslateBinary(linq, ODataBinaryOp.Or)); case ExpressionType.AndAlso: return(this.TranslateBinary(linq, ODataBinaryOp.And)); case ExpressionType.Add: if (linq.Type == typeof(string)) { // special case adding strings, since that's the same as Concat var binary = (BinaryExpression)linq; return(this.TranslateInternal(Expression.Call(Helpers.GetMethod(() => string.Concat(default(string), default(string))), binary.Left, binary.Right))); } return(this.TranslateBinary(linq, ODataBinaryOp.Add)); case ExpressionType.Subtract: return(this.TranslateBinary(linq, ODataBinaryOp.Subtract)); case ExpressionType.Multiply: return(this.TranslateBinary(linq, ODataBinaryOp.Multiply)); case ExpressionType.Divide: return(this.TranslateBinary(linq, ODataBinaryOp.Divide)); case ExpressionType.Modulo: return(this.TranslateBinary(linq, ODataBinaryOp.Modulo)); case ExpressionType.Equal: return(this.TranslateBinary(linq, ODataBinaryOp.Equal)); case ExpressionType.NotEqual: return(this.TranslateBinary(linq, ODataBinaryOp.NotEqual)); case ExpressionType.LessThan: return(this.TranslateBinary(linq, ODataBinaryOp.LessThan)); case ExpressionType.LessThanOrEqual: return(this.TranslateBinary(linq, ODataBinaryOp.LessThanOrEqual)); case ExpressionType.GreaterThan: return(this.TranslateBinary(linq, ODataBinaryOp.GreaterThan)); case ExpressionType.GreaterThanOrEqual: return(this.TranslateBinary(linq, ODataBinaryOp.GreaterThanOrEqual)); case ExpressionType.Not: return(ODataExpression.UnaryOp(this.TranslateInternal(((UnaryExpression)linq).Operand), ODataUnaryOp.Not)); case ExpressionType.Negate: // we translate -a.B as (-1 * a.B) for numeric types var negated = ((UnaryExpression)linq).Operand; var underlyingNegatedType = Nullable.GetUnderlyingType(negated.Type) ?? negated.Type; if (!underlyingNegatedType.IsNumeric()) { throw new ODataCompileException("Expression '" + linq + "' could not be translated to OData: only negation of numeric types is supported"); } var multiplyNegate = Expression.Multiply( Expression.Constant(Convert.ChangeType(-1, underlyingNegatedType), negated.Type), negated ); return(this.TranslateInternal(multiplyNegate)); case ExpressionType.MemberAccess: return(this._memberAndParameterTranslator.TranslateMemberAccess((MemberExpression)linq)); case ExpressionType.Convert: case ExpressionType.ConvertChecked: case ExpressionType.TypeAs: return(ODataExpression.Convert(this.TranslateInternal(((UnaryExpression)linq).Operand), linq.Type)); case ExpressionType.Parameter: return(this._memberAndParameterTranslator.TranslateParameter((ParameterExpression)linq)); case ExpressionType.TypeIs: var typeBinary = (TypeBinaryExpression)linq; var isArg = this.TranslateInternal(typeBinary.Expression); var isTypeConstant = ODataExpression.Constant(typeBinary.TypeOperand); return(isArg != null ? ODataExpression.Call(ODataFunction.IsOf, new[] { isArg, isTypeConstant }) : ODataExpression.Call(ODataFunction.IsOf, new[] { isTypeConstant })); case ExpressionType.Call: return(this.TranslateCall((MethodCallExpression)linq)); case ExpressionType.Quote: var quoted = (LambdaExpression)((UnaryExpression)linq).Operand; if (!this._isInsideQuery) { throw new ODataCompileException("Unexpected placement for lambda expression " + linq); } return(this.TranslateInternal(quoted.Body)); default: throw new ODataCompileException("Expression '" + linq + "' of type " + linq.NodeType + " could not be translated to OData"); } }
private ODataExpression TranslateCall(MethodCallExpression call) { // query operators if (call.Method.DeclaringType == typeof(Queryable)) { if (this._isInsideQuery) { throw new ODataCompileException("OData does not support nested query structures!"); } // normalize overloads of query operators bool changedAllToAny; var normalized = QueryOperatorCanonicalizer.Canonicalize(call, changedAllToAny: out changedAllToAny); if (normalized != call) { // TODO FUTURE better exception message here since expression being translated won't match? var result = this.TranslateInternal(normalized); if (changedAllToAny) { var innerResultTranslator = this._resultTranslator; Throw <InvalidOperationException> .If(innerResultTranslator == null, "Sanity check: expected non-null result translator"); this._resultTranslator = (values, count) => !((bool)innerResultTranslator(values, count)); } return(result); } // handle "normal" query methods // translate the source query. If the source is a constant, that's the "root" of the query tree // e. g. that might be the OData equivalent of a DbSet or ObjectQuery constant var source = (ODataQueryExpression)this.TranslateInternal(call.Arguments[0]); switch (call.Method.Name) { case "Where": // not the index version if (call.Arguments[1].Type.IsGenericOfType(typeof(Func <, ,>))) { goto default; } var predicate = this.TranslateInsideQuery(call.Arguments[1]); if (source.OrderBy.Count > 0 || source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a filter after applying an OrderBy, Take, or Skip operation"); } return(source.Update(filter: source.Filter != null ? ODataExpression.BinaryOp(source.Filter, ODataBinaryOp.And, predicate) : predicate)); case "OrderBy": case "OrderByDescending": case "ThenBy": case "ThenByDescending": if (call.Arguments.Count != 2) { goto default; } var sortKeyExpression = this.TranslateInsideQuery(call.Arguments[1]); var sortKey = ODataExpression.SortKey(sortKeyExpression, descending: call.Method.Name.EndsWith("Descending")); if (source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a sort after applying Take or Skip operations"); } return(source.Update( orderBy: call.Method.Name.StartsWith("Then") ? source.OrderBy.Concat(sortKey.Enumerate()) : sortKey.Enumerate() )); case "Skip": object skip; Throw <InvalidOperationException> .If(!TryGetValueFast(call.Arguments[1], out skip), "Could not get value"); if (source.Top.HasValue) { throw new ODataCompileException("Cannot apply a skip after applying a Take operation"); } return(source.Update(skip: source.Skip + (int)skip)); // not right case "Take": object take; Throw <InvalidOperationException> .If(!TryGetValueFast(call.Arguments[1], out take), "Could not get value"); return(Take(source, (int)take)); case "Select": /* * MA: select is tricky in OData, since it doesn't just conform to the OData $select system query option. * Instead, you could have an intermediate select in your query, and it would be nice to be able to support that. * In that case, OData still forces us to select original columns, but we can store away the projection expressions * to re-apply later. We also need to make sure to store how to map each projected property, so that we can inline these * projections. * * For example, imagine you have the query q.Select(x => new { a = x.B + 2 }).Where(t => t.a > 5). * In OData, this would translate to "$filter=B + 2 > 5&$select=B". We can then do the final projection to the anonymous * type in memory on the client side */ // we don't support select with index if (!call.Arguments[1].Type.GetGenericArguments(typeof(Expression <>)).Single().IsGenericOfType(typeof(Func <,>))) { goto default; } // unquote and extract the lambda var projection = ((LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand); // register the projection this._isInsideQuery = true; this._memberAndParameterTranslator.RegisterProjection(projection); this._isInsideQuery = false; // return the source, since the projection doesn't actually affect the returned expression // until the very end when we can use it to determine which columns to $select return(source); case "Any": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Any()); return(Take(source, 1)); case "Count": this._resultTranslator = (values, count) => ComputeCount(count: count.Value, skip: source.Skip, top: source.Top); // top 0 is so that we don't have any wasted payload of data that we won't look at return(source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0)); case "LongCount": this._resultTranslator = (values, count) => (long)ComputeCount(count: count.Value, skip: source.Skip, top: source.Top); // top 0 is so that we don't have any wasted payload of data that we won't look at return(source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0)); case "First": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.First()); return(Take(source, 1)); case "FirstOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.FirstOrDefault()); return(Take(source, 1)); case "Single": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Single());; return(Take(source, 2)); case "SingleOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.SingleOrDefault());; return(Take(source, 2)); default: throw new ODataCompileException("Query operator " + call.Method + " is not supported in OData"); } } // other OData methods // Enumerable/Collection contains (e. g. "IN"). This gets handled before the odata functions because // the thisExpression can't be translated normally. Since Contains() is declared on a bunch of different collection // types, we basically check that (1) there are either 2 arguments (static method) or an instance + 1 argument // (2) that the container argument/instance is an in-memory "constant", (3) that the collection object has an IEnumerable<T> // element type, and (4) that the test argument is of that element type // Finally, we translate the IN clause to a set of ORs object enumerable; Type elementType; if (call.Method.Name == "Contains" && call.Arguments.Count + Convert.ToInt32(!call.Method.IsStatic) == 2 && TryGetValueFast(call.Object ?? call.Arguments[0], out enumerable) && enumerable != null && (elementType = enumerable.GetType().GetGenericArguments(typeof(IEnumerable <>)).SingleOrDefault()) != null && call.Arguments.Last().Type == elementType) { var testExpression = this.TranslateInternal(call.Arguments.Last()); var equalsElementExpressions = ((IEnumerable)enumerable).Cast <object>() .Select(o => ODataExpression.Constant(o, elementType)) .Select(c => ODataExpression.BinaryOp(testExpression, ODataBinaryOp.Equal, c)) .ToArray(); var equivalentOrExpression = equalsElementExpressions.Length == 0 ? ODataExpression.Constant(true).As <ODataExpression>() : equalsElementExpressions.Aggregate((e1, e2) => ODataExpression.BinaryOp(e1, ODataBinaryOp.Or, e2)); return(equivalentOrExpression); } // ODataFunctions var thisExpression = this.TranslateInternal(call.Object); var translatedArgs = call.Arguments.Select(this.TranslateInternal); // string functions if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Substring") { return(ODataExpression.Call(ODataFunction.Substring, thisExpression.Enumerate().Concat(translatedArgs))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Replace" && call.Arguments[0].Type == typeof(string)) { // for now, we don't support the char replace overload return(ODataExpression.Call(ODataFunction.Replace, thisExpression.Enumerate().Concat(translatedArgs))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Concat" && call.Arguments.All(a => a.Type == typeof(string))) { // we support only string concats, but with any fixed number of parameters return(translatedArgs.Aggregate((s1, s2) => ODataExpression.Call(ODataFunction.Concat, new[] { s1, s2 }))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "StartsWith" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.StartsWith, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "EndsWith" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.EndsWith, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "IndexOf" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { return(ODataExpression.Call(ODataFunction.IndexOf, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Contains" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { // note: we reverse the args here because A.SubstringOf(B) is equivalent to B.Contains(A) return(ODataExpression.Call(ODataFunction.SubstringOf, new[] { translatedArgs.Single(), thisExpression })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToLower" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.ToLower, thisExpression.Enumerate())); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToUpper" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.ToUpper, thisExpression.Enumerate())); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Trim" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.Trim, thisExpression.Enumerate())); } // math functions if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Ceiling") { return(ODataExpression.Call(ODataFunction.Ceiling, translatedArgs)); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Floor") { return(ODataExpression.Call(ODataFunction.Floor, translatedArgs)); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Round" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.Round, translatedArgs)); } throw new ODataCompileException("Method " + call.Method + " could not be translated to OData"); }