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