private static OrderByBinderResult BindOrderBy <T>(string orderBy, IEdmModel model, ODataQuerySettings querySettings = null, IAssemblyResolver assembliesResolver = null) { Type elementType = typeof(T); OrderByClause orderByClause = CreateOrderByNode(orderBy, model, elementType); Assert.NotNull(orderByClause); querySettings = querySettings ?? _defaultSettings; QueryBinderContext context = new QueryBinderContext(model, querySettings, elementType) { AssembliesResolver = assembliesResolver, }; IOrderByBinder orderByBinder = new OrderByBinder(); return(orderByBinder.BindOrderBy(orderByClause, context)); }
private static Expression BindFilter <T>(string filter, IEdmModel model, ODataQuerySettings querySettings = null, IAssemblyResolver assembliesResolver = null) { Type elementType = typeof(T); FilterClause orderByClause = CreateFilterClause(filter, model, elementType); Assert.NotNull(orderByClause); querySettings = querySettings ?? _defaultSettings; QueryBinderContext context = new QueryBinderContext(model, querySettings, elementType) { AssembliesResolver = assembliesResolver, }; IFilterBinder filterBinder = new FilterBinder(); return(filterBinder.BindFilter(orderByClause, context)); }
public Expression BindSearch(SearchClause searchClause, QueryBinderContext context) { SearchTermNode node = searchClause.Expression as SearchTermNode; if (node != null) { // Lambda expression methods: if (_categories.Contains(node.Text)) { // Be noted: ToLowerInvariant is not enabled/supported in EF Core. // Switch to use "ToLower()" should work in EF Core. Expression <Func <Product, bool> > exp = p => p.Category.ToString().ToLowerInvariant() == node.Text.ToLowerInvariant(); return(exp); } else if (_colors.Contains(node.Text)) { Expression <Func <Product, bool> > exp = p => p.Color.ToString().ToLowerInvariant() == node.Text.ToLowerInvariant(); return(exp); } else if (context.ElementClrType == typeof(Sale)) { try { int year = int.Parse(node.Text); Expression <Func <Sale, bool> > exp = s => s.SaleDate.Year == year; return(exp); } catch { return(Expression.Constant(false, typeof(bool))); } } throw new InvalidOperationException("Unknown search on product or sale!"); } else { // Linq expression tree methods: Expression exp = BindSingleValueNode(searchClause.Expression, context); LambdaExpression lambdaExp = Expression.Lambda(exp, context.CurrentParameter); return(lambdaExp); } }
public void OrderByBinder_BindsComputedPropertyInDollarOrderBy_FromDollarCompute() { // Arrange QueryClause clause = CreateQueryClause("$orderby=Total&$compute=Price mul Qty as Total", _model, typeof(ComputeCustomer)); ODataQuerySettings querySettings = new ODataQuerySettings(); IOrderByBinder binder = new OrderByBinder(); QueryBinderContext context = new QueryBinderContext(_model, querySettings, typeof(ComputeCustomer)); if (clause.Compute != null) { context.AddComputedProperties(clause.Compute.ComputedItems); } // Act OrderByBinderResult orderByResult = binder.BindOrderBy(clause.OrderBy, context); // Assert Assert.Null(orderByResult.ThenBy); Assert.Equal(OrderByDirection.Ascending, orderByResult.Direction); string resultExpression = ExpressionStringBuilder.ToString(orderByResult.OrderByExpression); Assert.Equal("$it => ($it.Price * Convert($it.Qty))", resultExpression); IEnumerable <ComputeCustomer> customers = new[] { new ComputeCustomer { Id = 1, Qty = 3, Price = 5.99 }, new ComputeCustomer { Id = 2, Qty = 5, Price = 2.99 }, new ComputeCustomer { Id = 3, Qty = 2, Price = 18.01 }, new ComputeCustomer { Id = 4, Qty = 4, Price = 9.99 }, }; var orderedResult = OrderByBinderTests.InvokeOrderBy(customers, orderByResult.OrderByExpression, orderByResult.Direction, false); Assert.True(new [] { 2, 1, 3, 4 }.SequenceEqual(orderedResult.Select(a => a.Id))); // ordered id }
public Expression BindSearchTerm(SearchTermNode node, QueryBinderContext context) { // Source is from context; Expression source = context.CurrentParameter; string text = node.Text.ToLowerInvariant(); if (_categories.Contains(text)) { // $it.Category Expression categoryProperty = Expression.Property(source, "Category"); // $it.Category.Name Expression categoryName = Expression.Property(categoryProperty, "Name"); // string.Equals($it.Category.Name, text, StringComparison.OrdinalIgnoreCase); return(Expression.Call(null, StringEqualsMethodInfo, categoryName, Expression.Constant(text, typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase, typeof(StringComparison)))); } else if (_colors.Contains(text)) { // $it.Color Expression colorProperty = Expression.Property(source, "Color"); // $it.Color.ToString() Expression colorPropertyString = Expression.Call(colorProperty, "ToString", typeArguments: null, arguments: null); // string.Equals($it.Color.ToString(), text, StringComparison.OrdinalIgnoreCase); return(Expression.Call(null, StringEqualsMethodInfo, colorPropertyString, Expression.Constant(text, typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase, typeof(StringComparison)))); } else { return(Expression.Constant(false, typeof(bool))); } }
public override Expression BindBinaryOperatorNode(BinaryOperatorNode binaryOperatorNode, QueryBinderContext context) { Expression left = Bind(binaryOperatorNode.Left, context); Expression right = Bind(binaryOperatorNode.Right, context); if (BinaryOperatorMapping.TryGetValue(binaryOperatorNode.OperatorKind, out ExpressionType binaryExpressionType)) { return(Expression.MakeBinary(binaryExpressionType, left, right)); } throw new NotImplementedException($"Binary operator '{binaryOperatorNode.OperatorKind}' is not supported!"); }
public void SelectExpandBinder_BindsComputedPropertyInExpand_FromDollarCompute() { // Arrange QueryClause clause = CreateQueryClause("$expand=Orders($select=Title,Tax;$compute=Amount mul TaxRate as Tax)", _model, typeof(ComputeCustomer)); ODataQuerySettings querySettings = new ODataQuerySettings(); SelectExpandBinder binder = new SelectExpandBinder(); QueryBinderContext context = new QueryBinderContext(_model, querySettings, typeof(ComputeCustomer)); if (clause.Compute != null) { context.AddComputedProperties(clause.Compute.ComputedItems); } // Act Expression selectExp = binder.BindSelectExpand(clause.SelectExpand, context); // Assert Assert.NotNull(selectExp); string resultExpression = ExpressionStringBuilder.ToString(selectExp); Assert.Equal("$it => new SelectAllAndExpand`1() {Model = Microsoft.OData.Edm.EdmModel, " + "Instance = $it, UseInstanceForProperties = True, " + "Container = new NamedPropertyWithNext0`1() " + "{" + "Name = Orders, " + "Value = $it.Orders.Select($it => new SelectSome`1() " + "{" + "Model = Microsoft.OData.Edm.EdmModel, " + "Container = new NamedPropertyWithNext1`1() " + "{" + "Name = Title, " + "Value = $it.Title, " + "Next0 = new NamedProperty`1() {Name = Tax, Value = (Convert($it.Amount) * $it.TaxRate), }, " + "Next1 = new AutoSelectedNamedProperty`1() " + "{" + "Name = Id, Value = Convert($it.Id), " + "}, " + "}, " + "}), " + "Next0 = new NamedProperty`1() {Name = Dynamics, Value = $it.Dynamics, }, }, }", resultExpression); ComputeCustomer customer = new ComputeCustomer { Orders = new List <ComputeOrder> { new ComputeOrder { Title = "Kerry", Amount = 4, TaxRate = 0.35 }, new ComputeOrder { Title = "WU", Amount = 6, TaxRate = 0.5 }, new ComputeOrder { Title = "XU", Amount = 5, TaxRate = 0.12 }, } }; IDictionary <string, object> result = SelectExpandBinderTest.InvokeSelectExpand(customer, selectExp); Assert.Equal(8, result.Count); // Because it's select-all int idx = 0; var ordersValue = result["Orders"] as IEnumerable; foreach (var order in ordersValue) { SelectExpandWrapper itemWrapper = order as SelectExpandWrapper; var orderDic = itemWrapper.ToDictionary(); Assert.Equal(customer.Orders[idx].Title, orderDic["Title"]); Assert.Equal(customer.Orders[idx].Amount * customer.Orders[idx].TaxRate, orderDic["Tax"]); idx++; } }
private static IQueryable AddOrderByQueryForProperty(IOrderByBinder orderByBinder, OrderByClause orderbyClause, IQueryable querySoFar, QueryBinderContext binderContext, bool alreadyOrdered) { // Remove Thenby (make Thenby == null) to make sure we only apply the top orderby // TODO: need to refactor it later. orderbyClause = new OrderByClause(null, orderbyClause.Expression, orderbyClause.Direction, orderbyClause.RangeVariable); querySoFar = orderByBinder.ApplyBind(querySoFar, orderbyClause, binderContext, alreadyOrdered); return(querySoFar); }
private IOrderedQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings) { if (Context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } ICollection <OrderByNode> nodes = OrderByNodes; bool alreadyOrdered = false; IQueryable querySoFar = query; HashSet <object> propertiesSoFar = new HashSet <object>(); HashSet <string> openPropertiesSoFar = new HashSet <string>(); bool orderByItSeen = false; IOrderByBinder binder = Context.GetOrderByBinder(); QueryBinderContext binderContext = new QueryBinderContext(Context.Model, querySettings, Context.ElementClrType); if (Compute != null) { binderContext.AddComputedProperties(Compute.ComputeClause.ComputedItems); } foreach (OrderByNode node in nodes) { OrderByPropertyNode propertyNode = node as OrderByPropertyNode; OrderByOpenPropertyNode openPropertyNode = node as OrderByOpenPropertyNode; OrderByCountNode countNode = node as OrderByCountNode; if (propertyNode != null) { // Use autonomy class to achieve value equality for HasSet. var edmPropertyWithPath = new { propertyNode.Property, propertyNode.PropertyPath }; OrderByDirection direction = propertyNode.Direction; // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows if (propertiesSoFar.Contains(edmPropertyWithPath)) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, edmPropertyWithPath.PropertyPath)); } propertiesSoFar.Add(edmPropertyWithPath); if (propertyNode.OrderByClause != null) { querySoFar = AddOrderByQueryForProperty(binder, propertyNode.OrderByClause, querySoFar, binderContext, alreadyOrdered); } else { // could have ensure stable orderby property added querySoFar = ExpressionHelpers.OrderByProperty(querySoFar, Context.Model, edmPropertyWithPath.Property, direction, Context.ElementClrType, alreadyOrdered); } alreadyOrdered = true; } else if (openPropertyNode != null) { // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows if (openPropertiesSoFar.Contains(openPropertyNode.PropertyName)) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, openPropertyNode.PropertyPath)); } openPropertiesSoFar.Add(openPropertyNode.PropertyName); Contract.Assert(openPropertyNode.OrderByClause != null); querySoFar = AddOrderByQueryForProperty(binder, openPropertyNode.OrderByClause, querySoFar, binderContext, alreadyOrdered); alreadyOrdered = true; } else if (countNode != null) { Contract.Assert(countNode.OrderByClause != null); querySoFar = AddOrderByQueryForProperty(binder, countNode.OrderByClause, querySoFar, binderContext, alreadyOrdered); alreadyOrdered = true; } else { // This check prevents queries with duplicate nodes (e.g. $orderby=$it,$it,$it,$it...) from causing stack overflows if (orderByItSeen) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateIt)); } querySoFar = ExpressionHelpers.OrderByIt(querySoFar, node.Direction, Context.ElementClrType, alreadyOrdered); alreadyOrdered = true; orderByItSeen = true; } } return(querySoFar as IOrderedQueryable); }
public override Expression BindBinaryOperatorNode(BinaryOperatorNode binaryOperatorNode, QueryBinderContext context) { Expression left = Bind(binaryOperatorNode.Left, context); Expression right = Bind(binaryOperatorNode.Right, context); return(ExpressionBinderHelper.CreateBinaryExpression(binaryOperatorNode.OperatorKind, left, right, liftToNull: false, context.QuerySettings)); }
/// <summary> /// Apply the apply query to the given IQueryable. /// </summary> /// <remarks> /// The <see cref="ODataQuerySettings.HandleNullPropagation"/> property specifies /// how this method should handle null propagation. /// </remarks> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The <see cref="ODataQuerySettings"/> that contains all the query application related settings.</param> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull(nameof(query)); } if (querySettings == null) { throw Error.ArgumentNull(nameof(querySettings)); } if (Context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } // Linq to SQL not supported for $apply if (query.Provider.GetType().Namespace == HandleNullPropagationOptionHelper.Linq2SqlQueryProviderNamespace) { throw Error.NotSupported(SRResources.ApplyQueryOptionNotSupportedForLinq2SQL); } ApplyClause applyClause = ApplyClause; Contract.Assert(applyClause != null); // The IWebApiAssembliesResolver service is internal and can only be injected by WebApi. // This code path may be used in cases when the service container is not available // and the service container is available but may not contain an instance of IWebApiAssembliesResolver. IAssemblyResolver assembliesResolver = AssemblyResolverHelper.Default; if (Context.RequestContainer != null) { IAssemblyResolver injectedResolver = Context.RequestContainer.GetService <IAssemblyResolver>(); if (injectedResolver != null) { assembliesResolver = injectedResolver; } } foreach (var transformation in applyClause.Transformations) { if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) { var binder = new AggregationBinder(querySettings, assembliesResolver, ResultClrType, Context.Model, transformation); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; } else if (transformation.Kind == TransformationNodeKind.Compute) { var binder = new ComputeBinder(querySettings, assembliesResolver, ResultClrType, Context.Model, (ComputeTransformationNode)transformation); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; } else if (transformation.Kind == TransformationNodeKind.Filter) { var filterTransformation = transformation as FilterTransformationNode; IFilterBinder binder = Context.GetFilterBinder(); QueryBinderContext binderContext = new QueryBinderContext(Context.Model, querySettings, ResultClrType); query = binder.ApplyBind(query, filterTransformation.FilterClause, binderContext); } } return(query); }