コード例 #1
0
 public void TestEmpty()
 {
     Assert.IsEmpty(Traverse.Along(default(object), _ => throw new InvalidOperationException("should never get here")));
     Assert.IsEmpty(Traverse.DepthFirst(1, _ => Enumerable.Empty <int>()).Skip(1));
     Assert.IsEmpty(Traverse.BreadthFirst(1, _ => Enumerable.Empty <int>()).Skip(1));
     Assert.IsEmpty(Traverse.BreadthFirst <char>(Enumerable.Empty <char>(), _ => throw new InvalidOperationException("should never get here")));
 }
コード例 #2
0
        public void TestPassedPipelineIsUsed()
        {
            var mock      = new Mock <IODataClientQueryPipeline>(MockBehavior.Strict);
            var context   = new ODataQueryContext(mock.Object);
            var agg       = UnitTestHelpers.AssertThrows <AggregateException>(() => context.Query(new Uri("http://localhost:80")).ToArray());
            var innerMost = Traverse.Along(agg.As <Exception>(), e => e.InnerException).Last();

            Assert.That(innerMost, Is.InstanceOf <MockException>());
        }
コード例 #3
0
                protected override void VisitMemberAccess(ODataMemberAccessExpression node)
                {
                    var path = Traverse.Along(node, e => e.Expression)
                               .Reverse()
                               .Select(e => e.Member)
                               .ToArray();

                    this._paths.Add(path);
                }
コード例 #4
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");
            }
コード例 #5
0
        public void TestAlong()
        {
            Assert.Throws <ArgumentNullException>(() => Traverse.Along("a", null !));

            var ex = new Exception("a", new Exception("b", new Exception("c")));

            CollectionAssert.AreEqual(
                new[] { ex, ex.InnerException, ex.InnerException.InnerException },
                Traverse.Along(ex, e => e.InnerException)
                );

            CollectionAssert.AreEqual(
                Enumerable.Empty <Exception>(),
                Traverse.Along(default(Exception), e => e.InnerException)
                );
        }
コード例 #6
0
        private static NewExpression BuildNewExpression(ParameterExpression rootParameter, IReadOnlyDictionary <PropertyPath, ODataSelectColumnExpression> mapping)
        {
            // all paths longer than length 1 get grouped together and mapped to their own expression (this lets us support an arbitrary number of selected columns)
            var nestedProperties = mapping.Where(kvp => kvp.Key.Count > 1).ToArray();
            var nestedExpression = nestedProperties.Length > 0
                ? BuildNewExpression(rootParameter, nestedProperties.ToDictionary(kvp => kvp.Key.Skip(1).ToArray().As <PropertyPath>(), kvp => kvp.Value, PathComparer))
                : Expression.Constant(false).As <Expression>();

            // gather all argument expressions which will be used to initialize the type
            var arguments = ProjectionTypeProperties.Take(ProjectionTypeProperties.Count - 1)
                            .Select(prop =>
            {
                ODataSelectColumnExpression selectColumn;
                if (!mapping.TryGetValue(new[] { prop }, out selectColumn))
                {
                    return(Expression.Constant(false));
                }

                var selectColumnPropertyPath = Traverse.Along(selectColumn.Expression, c => c.Expression)
                                               .Select(m => m.Member)
                                               .Reverse();
                var expression = selectColumnPropertyPath.Aggregate(rootParameter.As <Expression>(), (acc, pi) => Expression.MakeMemberAccess(acc, pi));
                return(expression);
            })
                            .ToList();

            arguments.Add(nestedExpression);

            // build the new expression
            var parameterizedType = ProjectionType.MakeGenericType(arguments.Select(a => a.Type).ToArray());
            var newExpression     = Expression.New(
                parameterizedType.GetConstructors().Single(),
                arguments,
                members: parameterizedType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                );

            return(newExpression);
        }
コード例 #7
0
        private void TestProjection(params MemberExpression[] selections)
        {
            // convert selections to expressions
            var selectColumns = selections.Select(ToODataExpression).ToArray();

            // get query
            var items = Enumerable.Range(0, 10).Select(_ => new A()).ToArray();

            // project
            var result      = ODataQueryProjector.Project(items.AsQueryable(), selectColumns);
            var resultItems = result.Query.Cast <object>().ToArray();

            // validate
            var lambdas = selections.Select(exp => exp == null
                    ? a => a
                    : Expression.Lambda <Func <A, object> >(
                                                Expression.Convert(exp, typeof(object)),
                                                (ParameterExpression)Traverse.Along(exp, e => e.Expression as MemberExpression).Last().Expression
                                                ).Compile()
                                            )
                          .ToArray();

            for (var i = 0; i < items.Length; ++i)
            {
                for (var j = 0; j < selectColumns.Length; ++j)
                {
                    var expected = lambdas[j](items[i]);
                    var cmp      = expected.NullSafe(o => ODataRoundTripTest.GetComparer(o.GetType()), EqualityComparer <object> .Default);

                    var path   = result.Mapping[selectColumns[j]];
                    var actual = path.Aggregate(seed: resultItems[i], func: (acc, prop) => prop.GetValue(acc));

                    cmp.Equals(actual, expected).ShouldEqual(true, actual + " vs. " + expected);
                }
            }
        }
コード例 #8
0
            private Dictionary <MemberPath, ODataExpression> ParseProjectionBody(Expression body)
            {
                var isAnonymousTypeProjection     = body.NodeType == ExpressionType.New && body.Type.IsAnonymous();
                var isObjectInitializerProjection = body.NodeType == ExpressionType.MemberInit;

                if (isAnonymousTypeProjection || isObjectInitializerProjection)
                {
                    Dictionary <MemberPath, Expression> pathToLinqMapping;
                    if (isAnonymousTypeProjection) // anonymous, like a => new { x = a.B }
                    {
                        // anonymous type creation is actually a new using a constructor whose arguments match the anonymous properties
                        var @new = (NewExpression)body;
                        pathToLinqMapping = @new.Constructor.GetParameters()
                                            .Select((v, i) => new { Value = v, Index = i })
                                            .ToDictionary(
                            t => @new.Type.GetMember(t.Value.Name).As <MemberPath>(),
                            t => @new.Arguments[t.Index]
                            );
                    }
                    else
                    {
                        // initializer, like a => new X { Foo = a.B }
                        var memberInit = (MemberInitExpression)body;
                        if (memberInit.NewExpression.Arguments.Count > 0)
                        {
                            throw new ODataCompileException("Only parameterless constructors are supported with object initializers in OData. Found: " + memberInit);
                        }
                        if (memberInit.Bindings.Any(mb => mb.BindingType != MemberBindingType.Assignment))
                        {
                            throw new ODataCompileException("Only member assignment initializers are supported in OData. Found: " + memberInit);
                        }

                        pathToLinqMapping = memberInit.Bindings.Cast <MemberAssignment>()
                                            .ToDictionary(
                            mb => new[] { mb.Member }.As <MemberPath>(),
                            mb => mb.Expression
                            );
                    }

                    // for anonymous and initializer projections, we support nested projections such as
                    // a => new { b = new { c = a.x } } }
                    // To do this, for each property mapping (a.b in the example above), we simply recurse
                    // on the value expression and then add the property prefix to the resulting paths
                    var result = new Dictionary <MemberPath, ODataExpression>(MemberPathComparer);
                    foreach (var kvp in pathToLinqMapping)
                    {
                        var parsed = this.ParseProjectionBody(kvp.Value);
                        result.AddRange(parsed.Select(p => KeyValuePair.Create(kvp.Key.Concat(p.Key).ToArray().As <MemberPath>(), p.Value)));
                    }

                    return(result);
                }

                // if we have a path stack and we find a parameter, then we can just copy over all paths from
                // that parameters mapping to the new mapping
                if (this._pathStack.Count > 0 && body.NodeType == ExpressionType.Parameter)
                {
                    // add all paths for the last parameter
                    var result = new Dictionary <MemberPath, ODataExpression>(this._pathStack.Peek(), MemberPathComparer);
                    return(result);
                }

                // a member path, where the path stack is non-empty and thus the path won't translate directly
                // for example: a => a.b.x, where a is not the root query parameter
                if (this._pathStack.Count > 0 && body.NodeType == ExpressionType.MemberAccess)
                {
                    // pull out the member path as going back to a parameter (similar to what we do when translating a member)
                    var reverseMemberPath = Traverse.Along((MemberExpression)body, me => me.Expression as MemberExpression)
                                            .ToArray();
                    if (reverseMemberPath[reverseMemberPath.Length - 1].Expression.NodeType != ExpressionType.Parameter)
                    {
                        throw new ODataCompileException("Expression '" + reverseMemberPath.Last().Expression + "' could not be compiled to OData as part of a projection");
                    }

                    // find all paths for the current parameter which are prefixes of this path
                    var memberPath = reverseMemberPath.Reverse().Select(me => me.Member).ToArray();
                    var result     = this._pathStack.Peek().Where(kvp => StartsWith(kvp.Key, prefix: memberPath))
                                     .ToDictionary(
                        // the new mapping has the same path, but without the prefix of the current parameter
                        kvp => kvp.Key.Skip(memberPath.Length).ToArray(),
                        kvp => kvp.Value,
                        MemberPathComparer
                        );
                    // if we didn't find any such paths, then this should be directly translatable. For example:
                    // q.Select(a => a.B).Select(b => b.Id), then no path starts with b.Id. Thus, we fall through to
                    // just translating b.Id
                    if (result.Count > 0)
                    {
                        return(result);
                    }
                }

                // finally, if the projection doesn't match any special patterns, then we simply try
                // to translate the projected value directly (e. g. a => a.Id + 2)
                var simpleResult = new Dictionary <MemberPath, ODataExpression>(MemberPathComparer)
                {
                    { Empty <MemberInfo> .Array, this._translator.TranslateInternal(body) },
                };

                return(simpleResult);
            }
コード例 #9
0
 private bool IsAliasOf(Symbol a, NonTerminal b) =>
 Traverse.Along(a as NonTerminal, s => this.aliases.GetValueOrDefault(s)).Contains(b);