protected Expression BindAccessor(QueryNode node, Expression baseElement = null)
        {
            switch (node.Kind)
            {
            case QueryNodeKind.ResourceRangeVariableReference:
                return(this.LambdaParameter.Type.IsGenericType && this.LambdaParameter.Type.GetGenericTypeDefinition() == typeof(FlatteningWrapper <>)
                        ? (Expression)Expression.Property(this.LambdaParameter, "Source")
                        : this.LambdaParameter);

            case QueryNodeKind.SingleValuePropertyAccess:
                var propAccessNode = node as SingleValuePropertyAccessNode;
                return(CreatePropertyAccessExpression(BindAccessor(propAccessNode.Source, baseElement), propAccessNode.Property, GetFullPropertyPath(propAccessNode)));

            case QueryNodeKind.AggregatedCollectionPropertyNode:
                var aggPropAccessNode = node as AggregatedCollectionPropertyNode;
                return(CreatePropertyAccessExpression(BindAccessor(aggPropAccessNode.Source, baseElement), aggPropAccessNode.Property));

            case QueryNodeKind.SingleComplexNode:
                var singleComplexNode = node as SingleComplexNode;
                return(CreatePropertyAccessExpression(BindAccessor(singleComplexNode.Source, baseElement), singleComplexNode.Property, GetFullPropertyPath(singleComplexNode)));

            case QueryNodeKind.SingleValueOpenPropertyAccess:
                var openNode = node as SingleValueOpenPropertyAccessNode;
                return(GetFlattenedPropertyExpression(openNode.Name) ?? CreateOpenPropertyAccessExpression(openNode));

            case QueryNodeKind.None:
            case QueryNodeKind.SingleNavigationNode:
                var navNode = (SingleNavigationNode)node;
                return(CreatePropertyAccessExpression(BindAccessor(navNode.Source), navNode.NavigationProperty, GetFullPropertyPath(navNode)));

            case QueryNodeKind.BinaryOperator:
                var binaryNode      = (BinaryOperatorNode)node;
                var leftExpression  = BindAccessor(binaryNode.Left, baseElement);
                var rightExpression = BindAccessor(binaryNode.Right, baseElement);
                return(ExpressionBinderHelper.CreateBinaryExpression(binaryNode.OperatorKind, leftExpression, rightExpression,
                                                                     liftToNull: true, QuerySettings));

            case QueryNodeKind.Convert:
                var convertNode = (ConvertNode)node;
                return(CreateConvertExpression(convertNode, BindAccessor(convertNode.Source, baseElement)));

            case QueryNodeKind.CollectionNavigationNode:
                return(baseElement ?? this.LambdaParameter);

            case QueryNodeKind.SingleValueFunctionCall:
                return(BindSingleValueFunctionCallNode(node as SingleValueFunctionCallNode));

            case QueryNodeKind.Constant:
                return(BindConstantNode(node as ConstantNode));

            default:
                throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind,
                                         typeof(AggregationBinder).Name);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Binds a 'concat' 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 BindConcat(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "concat");

            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.Concat, context.QuerySettings, arguments));
        }
Esempio n. 3
0
        /// <summary>
        /// Binds a 'substring' 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 BindSubstring(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "substring");

            Expression[] arguments = BindArguments(node.Parameters, context);
            if (arguments[0].Type != typeof(string))
            {
                throw new ODataException(Error.Format(SRResources.FunctionNotSupportedOnEnum, node.Name));
            }

            ODataQuerySettings querySettings = context.QuerySettings;

            Expression functionCall;

            if (arguments.Length == 2)
            {
                Contract.Assert(ExpressionBinderHelper.IsInteger(arguments[1].Type));

                // When null propagation is allowed, we use a safe version of String.Substring(int).
                // But for providers that would not recognize custom expressions like this, we map
                // directly to String.Substring(int)
                if (context.QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True)
                {
                    // Safe function is static and takes string "this" as first argument
                    functionCall = ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.SubstringStartNoThrow, querySettings, arguments);
                }
                else
                {
                    functionCall = ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.SubstringStart, querySettings, arguments);
                }
            }
            else
            {
                // arguments.Length == 3 implies String.Substring(int, int)
                Contract.Assert(arguments.Length == 3 && ExpressionBinderHelper.IsInteger(arguments[1].Type) && ExpressionBinderHelper.IsInteger(arguments[2].Type));

                // When null propagation is allowed, we use a safe version of String.Substring(int, int).
                // But for providers that would not recognize custom expressions like this, we map
                // directly to String.Substring(int, int)
                if (querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
                {
                    // Safe function is static and takes string "this" as first argument
                    functionCall = ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLengthNoThrow, querySettings, arguments);
                }
                else
                {
                    functionCall = ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLength, querySettings, arguments);
                }
            }

            return(functionCall);
        }
Esempio n. 4
0
        /// <summary>
        /// Binds a 'time' 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 BindTime(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "time");

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

            // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec.
            Contract.Assert(arguments.Length == 1 && ExpressionBinderHelper.IsDateOrOffset(arguments[0].Type));

            // EF doesn't support new TimeOfDay(int, int, int, int), also doesn't support other property access, for example DateTimeOffset.DateTime.
            // Therefore, we just return the source (DateTime or DateTimeOffset).
            return(arguments[0]);
        }
Esempio n. 5
0
        /// <summary>
        /// Binds 'ceiling' 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 BindCeiling(SingleValueFunctionCallNode node, QueryBinderContext context)
        {
            CheckArgumentNull(node, context, "ceiling");

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

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

            MethodInfo ceiling = ExpressionBinderHelper.IsType <double>(arguments[0].Type)
                ? ClrCanonicalFunctions.CeilingOfDouble
                : ClrCanonicalFunctions.CeilingOfDecimal;

            return(ExpressionBinderHelper.MakeFunctionCall(ceiling, context.QuerySettings, arguments));
        }
Esempio n. 6
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);
        }
Esempio n. 7
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));
        }
Esempio n. 8
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));
        }
Esempio n. 9
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);
                }
            }
        }