示例#1
0
        /// <summary>
        /// Translates an OData $orderby represented by <see cref="OrderByClause"/> to <see cref="Expression"/>.
        /// $orderby=Age
        ///    |--  x => x.Age
        /// </summary>
        /// <param name="orderByClause">The orderby clause.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The OrderBy binder result, <see cref="OrderByBinderResult"/>.</returns>
        public virtual OrderByBinderResult BindOrderBy(OrderByClause orderByClause, QueryBinderContext context)
        {
            if (orderByClause == null)
            {
                throw Error.ArgumentNull(nameof(orderByClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            OrderByBinderResult head = null;
            OrderByBinderResult last = null;

            for (OrderByClause clause = orderByClause; clause != null; clause = clause.ThenBy)
            {
                Expression body = Bind(clause.Expression, context);

                ParameterExpression parameter = context.CurrentParameter;

                LambdaExpression orderByLambda = Expression.Lambda(body, parameter);

                OrderByBinderResult result = new OrderByBinderResult(orderByLambda, clause.Direction);

                if (head == null)
                {
                    head = result;
                    last = result;
                }
                else
                {
                    Contract.Assert(last != null);
                    last.ThenBy = result;
                    last        = result;
                }
            }

            return(head);
        }
        /// <summary>
        /// Translates an OData $filter represented by <see cref="FilterClause"/> to <see cref="Expression"/> and apply to <see cref="Expression" />.
        /// </summary>
        /// <param name="binder">The given filter binder.</param>
        /// <param name="source">The given source.</param>
        /// <param name="filterClause">The filter clause.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The applied result.</returns>
        public static Expression ApplyBind(this IFilterBinder binder, Expression source, FilterClause filterClause, QueryBinderContext context)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            if (filterClause == null)
            {
                throw Error.ArgumentNull(nameof(filterClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            Expression filterExp = binder.BindFilter(filterClause, context);

            Type elementType = context.ElementClrType;

            MethodInfo filterMethod;

            if (typeof(IQueryable).IsAssignableFrom(source.Type))
            {
                filterMethod = ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(elementType);
            }
            else
            {
                filterMethod = ExpressionHelperMethods.EnumerableWhereGeneric.MakeGenericMethod(elementType);
            }

            return(Expression.Call(filterMethod, source, filterExp));
        }
        /// <summary>
        /// Translates an OData $filter represented by <see cref="FilterClause"/> to <see cref="Expression"/> and apply to <see cref="IQueryable" />.
        /// </summary>
        /// <param name="binder">The given filter binder.</param>
        /// <param name="query">The given queryable.</param>
        /// <param name="filterClause">The filter clause.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The applied result.</returns>
        public static IQueryable ApplyBind(this IFilterBinder binder, IQueryable query, FilterClause filterClause, QueryBinderContext context)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (query == null)
            {
                throw Error.ArgumentNull(nameof(query));
            }

            if (filterClause == null)
            {
                throw Error.ArgumentNull(nameof(filterClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            Expression filterExp = binder.BindFilter(filterClause, context);

            return(ExpressionHelpers.Where(query, filterExp, context.ElementClrType));
        }
        /// <summary>
        /// Translate an OData $search parse tree represented by <see cref="SearchClause"/> to
        /// an <see cref="Expression"/> and applies it to an <see cref="IQueryable"/>.
        /// </summary>
        /// <param name="binder">The built in <see cref="ISelectExpandBinder"/></param>
        /// <param name="source">The original <see cref="IQueryable"/>.</param>
        /// <param name="searchClause">The OData $search parse tree.</param>
        /// <param name="context">An instance of the <see cref="QueryBinderContext"/>.</param>
        /// <returns>The applied result.</returns>
        public static IQueryable ApplyBind(this ISearchBinder binder, IQueryable source, SearchClause searchClause, QueryBinderContext context)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            if (searchClause == null)
            {
                throw Error.ArgumentNull(nameof(searchClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            Expression searchExp = binder.BindSearch(searchClause, context);

            return(ExpressionHelpers.Where(source, searchExp, context.ElementClrType));
        }
        /// <summary>
        /// Translates an OData $filter represented by <see cref="FilterClause"/> to <see cref="Expression"/> and apply to <see cref="IEnumerable" />.
        /// </summary>
        /// <param name="binder">The given filter binder.</param>
        /// <param name="query">The given IEnumerable.</param>
        /// <param name="filterClause">The filter clause.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The applied result.</returns>
        public static IEnumerable ApplyBind(this IFilterBinder binder, IEnumerable query, FilterClause filterClause, QueryBinderContext context)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (query == null)
            {
                throw Error.ArgumentNull(nameof(query));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            Expression filterExp = binder.BindFilter(filterClause, context);

            MethodInfo whereMethod = ExpressionHelperMethods.EnumerableWhereGeneric.MakeGenericMethod(context.ElementClrType);

            return(whereMethod.Invoke(null, new object[] { query, filterExp }) as IEnumerable);
        }
        /// <summary>
        /// Translate an OData $select or $expand parse tree represented by <see cref="SelectExpandClause"/> to
        /// an <see cref="Expression"/> and applies it to an <see cref="object"/>.
        /// </summary>
        /// <param name="binder">The built in <see cref="ISelectExpandBinder"/></param>
        /// <param name="source">The original <see cref="object"/>.</param>
        /// <param name="selectExpandClause">The OData $select or $expand parse tree.</param>
        /// <param name="context">An instance of the <see cref="QueryBinderContext"/>.</param>
        /// <returns></returns>
        public static object ApplyBind(this ISelectExpandBinder binder, object source, SelectExpandClause selectExpandClause, QueryBinderContext context)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            if (selectExpandClause == null)
            {
                throw Error.ArgumentNull(nameof(selectExpandClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            LambdaExpression projectionLambda = binder.BindSelectExpand(selectExpandClause, context) as LambdaExpression;

            return(projectionLambda.Compile().DynamicInvoke(source));
        }
        /// <summary>
        /// Translate an OData $select or $expand parse tree represented by <see cref="SelectExpandClause"/> to
        /// an <see cref="Expression"/> and applies it to an <see cref="IQueryable"/>. ALso <see cref="IQueryable"/>
        /// </summary>
        /// <param name="binder">The built in <see cref="ISelectExpandBinder"/></param>
        /// <param name="source">The original <see cref="IQueryable"/>.</param>
        /// <param name="selectExpandClause">The OData $select or $expand parse tree.</param>
        /// <param name="context">An instance of the <see cref="QueryBinderContext"/>.</param>
        /// <returns></returns>
        public static IQueryable ApplyBind(this ISelectExpandBinder binder, IQueryable source, SelectExpandClause selectExpandClause, QueryBinderContext context)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            if (selectExpandClause == null)
            {
                throw Error.ArgumentNull(nameof(selectExpandClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            Type elementType = context.ElementClrType;

            LambdaExpression projectionLambda = binder.BindSelectExpand(selectExpandClause, context) as LambdaExpression;

            MethodInfo selectMethod = ExpressionHelperMethods.QueryableSelectGeneric.MakeGenericMethod(elementType, projectionLambda.Body.Type);

            return(selectMethod.Invoke(null, new object[] { source, projectionLambda }) as IQueryable);
        }
        /// <summary>
        /// Translates an OData $orderby represented by <see cref="OrderByClause"/> to <see cref="Expression"/> and apply to <see cref="Expression" />.
        /// </summary>
        /// <param name="binder">The given filter binder.</param>
        /// <param name="source">The given source.</param>
        /// <param name="orderByClause">The filter clause.</param>
        /// <param name="context">The query binder context.</param>
        /// <param name="alreadyOrdered">The boolean value indicating whether it's ordered or not.</param>
        /// <returns>The applied result.</returns>
        public static Expression ApplyBind(this IOrderByBinder binder, Expression source, OrderByClause orderByClause, QueryBinderContext context, bool alreadyOrdered)
        {
            if (binder == null)
            {
                throw Error.ArgumentNull(nameof(binder));
            }

            if (source == null)
            {
                throw Error.ArgumentNull(nameof(source));
            }

            if (orderByClause == null)
            {
                throw Error.ArgumentNull(nameof(orderByClause));
            }

            if (context == null)
            {
                throw Error.ArgumentNull(nameof(context));
            }

            OrderByBinderResult orderByResult = binder.BindOrderBy(orderByClause, context);

            Type elementType           = context.ElementClrType;
            OrderByBinderResult result = orderByResult;

            do
            {
                LambdaExpression orderByExpression = result.OrderByExpression as LambdaExpression;
                Contract.Assert(orderByExpression != null);

                OrderByDirection direction = result.Direction;

                source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, direction, alreadyOrdered);

                alreadyOrdered = true;

                result = result.ThenBy;
            }while (result != null);

            return(source);
        }
示例#9
0
        /// <summary>
        /// Binds customized function to create a LINQ <see cref="Expression"/>.
        /// </summary>
        /// <param name="node">The query node to bind.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The LINQ <see cref="Expression"/> created.</returns>
        protected virtual Expression BindCustomMethodExpressionOrNull(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context);

            Expression[]       arguments           = BindArguments(node.Parameters, context);
            IEnumerable <Type> methodArgumentsType = arguments.Select(argument => argument.Type);

            // Search for custom method info that are binded to the node name
            MethodInfo methodInfo;

            if (UriFunctionsBinder.TryGetMethodInfo(node.Name, methodArgumentsType, out methodInfo))
            {
                return(ExpressionBinderHelper.MakeFunctionCall(methodInfo, context.QuerySettings, arguments));
            }

            return(null);
        }
示例#10
0
        /// <summary>
        /// Binds 'cast' function to create a LINQ <see cref="Expression"/>.
        /// </summary>
        /// <param name="node">The query node to bind.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The LINQ <see cref="Expression"/> created.</returns>
        protected virtual Expression BindCastSingleValue(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "cast");

            Expression[] arguments = BindArguments(node.Parameters, context);
            Contract.Assert(arguments.Length == 1 || arguments.Length == 2);

            Expression source         = arguments.Length == 1 ? context.CurrentParameter : arguments[0];
            string     targetTypeName = (string)((ConstantNode)node.Parameters.Last()).Value;
            IEdmType   targetEdmType  = context.Model.FindType(targetTypeName);
            Type       targetClrType  = null;

            if (targetEdmType != null)
            {
                IEdmTypeReference targetEdmTypeReference = targetEdmType.ToEdmTypeReference(false);
                targetClrType = context.Model.GetClrType(targetEdmTypeReference);

                if (source != NullConstant)
                {
                    if (source.Type == targetClrType)
                    {
                        return(source);
                    }

                    if ((!targetEdmTypeReference.IsPrimitive() && !targetEdmTypeReference.IsEnum()) ||
                        (context.Model.GetEdmPrimitiveTypeReference(source.Type) == null && !TypeHelper.IsEnum(source.Type)))
                    {
                        // Cast fails and return null.
                        return(NullConstant);
                    }
                }
            }

            if (targetClrType == null || source == NullConstant)
            {
                return(NullConstant);
            }

            if (targetClrType == typeof(string))
            {
                return(ExpressionBinderHelper.BindCastToStringType(source));
            }
            else if (TypeHelper.IsEnum(targetClrType))
            {
                return(BindCastToEnumType(source.Type, targetClrType, node.Parameters.First(), arguments.Length, context));
            }
            else
            {
                if (TypeHelper.IsNullable(source.Type) && !TypeHelper.IsNullable(targetClrType))
                {
                    // Make the target Clr type nullable to avoid failure while casting
                    // nullable source, whose value may be null, to a non-nullable type.
                    // For example: cast(NullableInt32Property,Edm.Int64)
                    // The target Clr type should be Nullable<Int64> rather than Int64.
                    targetClrType = typeof(Nullable <>).MakeGenericType(targetClrType);
                }

                try
                {
                    return(Expression.Convert(source, targetClrType));
                }
                catch (InvalidOperationException)
                {
                    // Cast fails and return null.
                    return(NullConstant);
                }
            }
        }
示例#11
0
        /// <summary>
        /// Binds 'fractionalseconds' function to create a LINQ <see cref="Expression"/>.
        /// </summary>
        /// <param name="node">The query node to bind.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The LINQ <see cref="Expression"/> created.</returns>
        protected virtual Expression BindFractionalSeconds(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "fractionalseconds");

            Expression[] arguments = BindArguments(node.Parameters, context);
            Contract.Assert(arguments.Length == 1 && (ExpressionBinderHelper.IsTimeRelated(arguments[0].Type)));

            // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec.
            Expression parameter = arguments[0];

            PropertyInfo property;

            if (ExpressionBinderHelper.IsTimeOfDay(parameter.Type))
            {
                property = ClrCanonicalFunctions.TimeOfDayProperties[ClrCanonicalFunctions.MillisecondFunctionName];
            }
            else if (ExpressionBinderHelper.IsDateTime(parameter.Type))
            {
                property = ClrCanonicalFunctions.DateTimeProperties[ClrCanonicalFunctions.MillisecondFunctionName];
            }
            else if (ExpressionBinderHelper.IsTimeSpan(parameter.Type))
            {
                property = ClrCanonicalFunctions.TimeSpanProperties[ClrCanonicalFunctions.MillisecondFunctionName];
            }
            else
            {
                property = ClrCanonicalFunctions.DateTimeOffsetProperties[ClrCanonicalFunctions.MillisecondFunctionName];
            }

            // Millisecond
            Expression milliSecond        = ExpressionBinderHelper.MakePropertyAccess(property, parameter, context.QuerySettings);
            Expression decimalMilliSecond = Expression.Convert(milliSecond, typeof(decimal));
            Expression fractionalSeconds  = Expression.Divide(decimalMilliSecond, Expression.Constant(1000m, typeof(decimal)));

            return(ExpressionBinderHelper.CreateFunctionCallWithNullPropagation(fractionalSeconds, arguments, context.QuerySettings));
        }
示例#12
0
        /// <summary>
        /// Binds time related functions to create a LINQ <see cref="Expression"/>.
        /// </summary>
        /// <param name="node">The query node to bind.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The LINQ <see cref="Expression"/> created.</returns>
        protected virtual Expression BindTimeRelatedProperty(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context);

            Expression[] arguments = BindArguments(node.Parameters, context);

            Contract.Assert(arguments.Length == 1 && ExpressionBinderHelper.IsTimeRelated(arguments[0].Type));

            // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec.
            Expression parameter = arguments[0];

            PropertyInfo property;

            if (ExpressionBinderHelper.IsTimeOfDay(parameter.Type))
            {
                Contract.Assert(ClrCanonicalFunctions.TimeOfDayProperties.ContainsKey(node.Name));
                property = ClrCanonicalFunctions.TimeOfDayProperties[node.Name];
            }
#if NET6_0
            else if (parameter.Type.IsTimeOnly())
            {
                Contract.Assert(ClrCanonicalFunctions.TimeOnlyProperties.ContainsKey(node.Name));
                property = ClrCanonicalFunctions.TimeOnlyProperties[node.Name];
            }
#endif
            else if (ExpressionBinderHelper.IsDateTime(parameter.Type))
            {
                Contract.Assert(ClrCanonicalFunctions.DateTimeProperties.ContainsKey(node.Name));
                property = ClrCanonicalFunctions.DateTimeProperties[node.Name];
            }
            else if (ExpressionBinderHelper.IsTimeSpan(parameter.Type))
            {
                Contract.Assert(ClrCanonicalFunctions.TimeSpanProperties.ContainsKey(node.Name));
                property = ClrCanonicalFunctions.TimeSpanProperties[node.Name];
            }
            else
            {
                Contract.Assert(ClrCanonicalFunctions.DateTimeOffsetProperties.ContainsKey(node.Name));
                property = ClrCanonicalFunctions.DateTimeOffsetProperties[node.Name];
            }

            return(ExpressionBinderHelper.MakeFunctionCall(property, context.QuerySettings, parameter));
        }
示例#13
0
        /// <summary>
        /// Binds a <see cref="SingleValueFunctionCallNode"/> to create a LINQ <see cref="Expression"/> that
        /// represents the semantics of the <see cref="SingleValueFunctionCallNode"/>.
        /// </summary>
        /// <param name="node">The query node to bind.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The LINQ <see cref="Expression"/> created.</returns>
        public virtual Expression BindSingleValueFunctionCallNode(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context);

            switch (node.Name)
            {
            case ClrCanonicalFunctions.StartswithFunctionName:
                return(BindStartsWith(node, context));

            case ClrCanonicalFunctions.EndswithFunctionName:
                return(BindEndsWith(node, context));

            case ClrCanonicalFunctions.ContainsFunctionName:
                return(BindContains(node, context));

            case ClrCanonicalFunctions.SubstringFunctionName:
                return(BindSubstring(node, context));

            case ClrCanonicalFunctions.LengthFunctionName:
                return(BindLength(node, context));

            case ClrCanonicalFunctions.IndexofFunctionName:
                return(BindIndexOf(node, context));

            case ClrCanonicalFunctions.TolowerFunctionName:
                return(BindToLower(node, context));

            case ClrCanonicalFunctions.ToupperFunctionName:
                return(BindToUpper(node, context));

            case ClrCanonicalFunctions.TrimFunctionName:
                return(BindTrim(node, context));

            case ClrCanonicalFunctions.ConcatFunctionName:
                return(BindConcat(node, context));

            case ClrCanonicalFunctions.YearFunctionName:
            case ClrCanonicalFunctions.MonthFunctionName:
            case ClrCanonicalFunctions.DayFunctionName:
                return(BindDateRelatedProperty(node, context));    // Date & DateTime & DateTimeOffset

            case ClrCanonicalFunctions.HourFunctionName:
            case ClrCanonicalFunctions.MinuteFunctionName:
            case ClrCanonicalFunctions.SecondFunctionName:
                return(BindTimeRelatedProperty(node, context));    // TimeOfDay & DateTime & DateTimeOffset

            case ClrCanonicalFunctions.FractionalSecondsFunctionName:
                return(BindFractionalSeconds(node, context));

            case ClrCanonicalFunctions.RoundFunctionName:
                return(BindRound(node, context));

            case ClrCanonicalFunctions.FloorFunctionName:
                return(BindFloor(node, context));

            case ClrCanonicalFunctions.CeilingFunctionName:
                return(BindCeiling(node, context));

            case ClrCanonicalFunctions.CastFunctionName:
                return(BindCastSingleValue(node, context));

            case ClrCanonicalFunctions.IsofFunctionName:
                return(BindIsOf(node, context));

            case ClrCanonicalFunctions.DateFunctionName:
                return(BindDate(node, context));

            case ClrCanonicalFunctions.TimeFunctionName:
                return(BindTime(node, context));

            case ClrCanonicalFunctions.NowFunctionName:
                return(BindNow(node, context));

            default:
                // Get Expression of custom binded method.
                Expression expression = BindCustomMethodExpressionOrNull(node, context);
                if (expression != null)
                {
                    return(expression);
                }

                throw new NotImplementedException(Error.Format(SRResources.ODataFunctionNotSupported, node.Name));
            }
        }
示例#14
0
        /// <summary>
        /// Binds a 'startswith' function to create a LINQ <see cref="Expression"/>.
        /// </summary>
        /// <param name="node">The query node to bind.</param>
        /// <param name="context">The query binder context.</param>
        /// <returns>The LINQ <see cref="Expression"/> created.</returns>
        protected virtual Expression BindStartsWith(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "startswith");

            Expression[] arguments = BindArguments(node.Parameters, context);
            ValidateAllStringArguments(node.Name, arguments);

            Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));

            return(ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.StartsWith, context.QuerySettings, arguments));
        }