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"))); }
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>()); }
protected override void VisitMemberAccess(ODataMemberAccessExpression node) { var path = Traverse.Along(node, e => e.Expression) .Reverse() .Select(e => e.Member) .ToArray(); this._paths.Add(path); }
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"); }
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) ); }
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); }
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); } } }
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); }
private bool IsAliasOf(Symbol a, NonTerminal b) => Traverse.Along(a as NonTerminal, s => this.aliases.GetValueOrDefault(s)).Contains(b);