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); } }
/// <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)); }
/// <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); }
/// <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]); }
/// <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)); }
/// <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); }
/// <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)); }
/// <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)); }
/// <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); } } }