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));
        }
예제 #2
0
            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");
            }
예제 #3
0
        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());
        }
예제 #4
0
        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);
        }