internal static ODataSelectColumnExpression ToODataExpression(MemberExpression selection) { Func <Expression, ODataMemberAccessExpression> translate = null; translate = exp => (exp as MemberExpression) == null ? null : ODataExpression.MemberAccess(translate(((MemberExpression)exp).Expression), (PropertyInfo)((MemberExpression)exp).Member); var memberAccess = translate(selection); return(ODataExpression.SelectColumn(memberAccess, allColumns: memberAccess == null || memberAccess.Type == ODataExpressionType.Complex)); }
public ODataExpression TranslateMemberAccess(MemberExpression memberAccess) { // translate special properties ODataExpression result; if (this.TryTranslateMemberAccessAsSpecialMember(memberAccess, out result)) { return(result); } // otherwise, try to translate as a member access chain going back to a parameter. Since the only special members // we support return types with no members (primitive types), this is a safe assertion var memberAccessChain = Traverse.Along(memberAccess, me => me.Expression as MemberExpression) .Reverse() .ToArray(); if (memberAccessChain[0].Expression.NodeType != ExpressionType.Parameter) { throw new ODataCompileException("Cannot compile member access path '" + memberAccessChain.Select(me => me.Member.Name).ToDelimitedString(".") + "' to OData: must start with a lambda parameter"); } // attempt to translate the full path by referencing the mapping in the path stack if (this._pathStack.Count > 0 && this._pathStack.Peek().TryGetValue(memberAccessChain.Select(me => me.Member).ToArray(), out result)) { return(result); } // translate the sub path, and then attempt to apply the current member // for example, if the path was param.A.B, then we'd translate param.A and then try to translate B as a property of A var instance = this._translator.TranslateInternal(memberAccess.Expression); var property = memberAccess.Member as PropertyInfo; if (property == null) { throw new ODataCompileException("Only properties are supported. Found: " + memberAccess.Member); } if ((property.GetMethod ?? property.SetMethod).IsStatic) { // we don't really expect to hit this case in practice, because static properties should be evaluated in memory // rather than translated throw new ODataCompileException("Static properties are not supported. Found: " + property); } if (instance == null || instance.Kind == ODataExpressionKind.MemberAccess) { return(ODataExpression.MemberAccess((ODataMemberAccessExpression)instance, property)); } throw new ODataCompileException("Property " + property + " is not supported in OData"); }
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()); }
public ODataExpression Translate(Expression linq, out IQueryable rootQuery, out ResultTranslator resultTranslator) { this._isInsideQuery = false; this._rootQuery = null; this._resultTranslator = null; this._memberAndParameterTranslator = new MemberAndParameterTranslator(this); // normalize away ODataRow constructs var normalized = ODataEntity.Normalize(linq); var translated = this.TranslateInternal(normalized); if (translated.Kind == ODataExpressionKind.Query) { var referencedPaths = this._memberAndParameterTranslator.GetReferencedMemberPathsInFinalProjection(); if (referencedPaths != null) { var selectColumns = referencedPaths.Select(p => p.Aggregate(default(ODataMemberAccessExpression), (e, m) => ODataExpression.MemberAccess(e, (PropertyInfo)m))) .Select(ma => ODataExpression.SelectColumn(ma, allColumns: ma.Type == ODataExpressionType.Complex)); translated = ((ODataQueryExpression)translated).Update(select: selectColumns); } } rootQuery = this._rootQuery; var projection = this._memberAndParameterTranslator.GetFinalProjection(); var finalTranslator = this._resultTranslator ?? ((values, count) => values); if (projection != null) { var selectMethod = Helpers.GetMethod((IEnumerable <object> e) => e.Select(o => o)) .GetGenericMethodDefinition() .MakeGenericMethod(projection.Type.GetGenericArguments(typeof(Func <,>))); // restores any ODataRow constructs that were normalized away, since we need to be able to compile and run the projection // (i. e. fake ODataRow property accesses don't run when compiled) var denormalizedProjection = (LambdaExpression)ODataEntity.Denormalize(projection); Func <object, object> queryTranslator = enumerable => selectMethod.Invoke(null, new[] { enumerable, denormalizedProjection.Compile() }); resultTranslator = (values, count) => finalTranslator((IEnumerable)queryTranslator(values), count); } else { resultTranslator = finalTranslator; } return(translated); }