Beispiel #1
0
 public ApplyBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)
 {
     this.bindMethod    = bindMethod;
     this.state         = state;
     this.filterBinder  = new FilterBinder(bindMethod, state);
     this.configuration = configuration;
     this.odataPathInfo = odataPathInfo;
 }
Beispiel #2
0
 private Expression AddOrderByQueryForSource(Expression source, OrderByClause orderbyClause, Type elementType)
 {
     if (orderbyClause != null)
     {
         LambdaExpression orderByExpression =
             FilterBinder.Bind(orderbyClause, elementType, _model, _settings);
         source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, orderbyClause.Direction);
     }
     return(source);
 }
Beispiel #3
0
        /// <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("query");
            }

            if (querySettings == null)
            {
                throw Error.ArgumentNull("querySettings");
            }

            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            ApplyClause applyClause = ApplyClause;

            Contract.Assert(applyClause != null);

            ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query);

            // 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.
            IWebApiAssembliesResolver assembliesResolver = WebApiAssembliesResolver.Default;

            if (Context.RequestContainer != null)
            {
                IWebApiAssembliesResolver injectedResolver = Context.RequestContainer.GetService <IWebApiAssembliesResolver>();
                if (injectedResolver != null)
                {
                    assembliesResolver = injectedResolver;
                }
            }

            foreach (var transformation in applyClause.Transformations)
            {
                if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy)
                {
                    var binder = new AggregationBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, transformation);
                    query = binder.Bind(query);
                    this.ResultClrType = binder.ResultClrType;
                }
                else if (transformation.Kind == TransformationNodeKind.Filter)
                {
                    var        filterTransformation = transformation as FilterTransformationNode;
                    Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, Context, querySettings);
                    query = ExpressionHelpers.Where(query, filter, ResultClrType);
                }
            }

            return(query);
        }
        public IQueryable Bind(IQueryable query, ApplyClause applyClause)
        {
            // groupby and aggregate transform input  by collapsing everything not used in groupby/aggregate
            // as a result we have two distinct cases for expand implementation
            // 1. Expands followed by groupby/aggregate with entity set aggregations => filters in expand need to be applied (pushed down) to corresponding entityset aggregations
            // 2. Mix of expands and filters w/o any groupby/aggregation => falling back to $expand behavior and could just use SelectExpandBinder
            bool inputShapeChanged = false;

            foreach (var transformation in applyClause.Transformations)
            {
                if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy)
                {
                    var binder = new AggregationBinder(_settings, _assembliesResolver, ResultClrType, _context.Model, transformation, _context, SelectExpandClause);
                    query = binder.Bind(query);
                    this.ResultClrType = binder.ResultClrType;
                    inputShapeChanged  = true;
                }
                else if (transformation.Kind == TransformationNodeKind.Compute)
                {
                    var binder = new ComputeBinder(_settings, _assembliesResolver, ResultClrType, _context.Model, (ComputeTransformationNode)transformation);
                    query = binder.Bind(query);
                    this.ResultClrType = binder.ResultClrType;
                    inputShapeChanged  = true;
                }
                else if (transformation.Kind == TransformationNodeKind.Filter)
                {
                    var        filterTransformation = (FilterTransformationNode)transformation;
                    Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, _context, _settings);
                    query = ExpressionHelpers.Where(query, filter, ResultClrType);
                }
                else if (transformation.Kind == TransformationNodeKind.Expand)
                {
                    var newClause = ((ExpandTransformationNode)transformation).ExpandClause;
                    if (SelectExpandClause == null)
                    {
                        SelectExpandClause = newClause;
                    }
                    else
                    {
                        SelectExpandClause = new SelectExpandClause(SelectExpandClause.SelectedItems.Concat(newClause.SelectedItems), false);
                    }
                }
            }

            if (SelectExpandClause != null && !inputShapeChanged)
            {
                var expandString = GetExpandsOnlyString(SelectExpandClause);

                var selectExpandQueryOption = new SelectExpandQueryOption(null, expandString, _context, SelectExpandClause);
                query = SelectExpandBinder.Bind(query, _settings, selectExpandQueryOption);
            }

            return(query);
        }
        /// <summary>
        /// Apply the filter query to the given IQueryable.
        /// </summary>
        /// <param name="query">The IQueryable that we are applying filter query against.</param>
        /// <param name="handleNullPropagation">Specifies if we need to handle null propagation. Pass false if the underlying query provider handles null propagation. Otherwise pass true.</param>
        /// <returns>The query that the filter query has been applied to.</returns>
        public IQueryable ApplyTo(IQueryable query, bool handleNullPropagation)
        {
            FilterQueryNode node = QueryNode;

            Contract.Assert(node != null);

            Expression filter = FilterBinder.Bind(node, Context.EntityClrType, Context.Model, handleNullPropagation);

            query = ExpressionHelpers.Where(query, filter, Context.EntityClrType);
            return(query);
        }
        private Expression AddOrderByQueryForSource(Expression source, OrderByClause orderbyClause, Type elementType)
        {
            if (orderbyClause != null)
            {
                // TODO: Implement proper support for $select/$expand after $apply
                LambdaExpression orderByExpression =
                    FilterBinder.Bind(null, orderbyClause, elementType, _context.RequestContainer);
                source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, orderbyClause.Direction);
            }

            return(source);
        }
        private static IQueryable AddOrderByQueryForProperty <T>(ODataQuery <T> query, ODataQuerySettings querySettings,
                                                                 OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered)
        {
            //Context.UpdateQuerySettings(querySettings, query);

            LambdaExpression orderByExpression =
                FilterBinder.Bind(query, orderbyClause, typeof(T), query.ServiceProvider);

            querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, typeof(T),
                                                   alreadyOrdered);
            return(querySoFar);
        }
Beispiel #8
0
        /// <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>
        /// <param name="assemblyProvider">The <see cref="IAssemblyProvider"/> to use.</param>
        /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns>
        public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssemblyProvider assemblyProvider)
        {
            if (query == null)
            {
                throw Error.ArgumentNull("query");
            }

            if (querySettings == null)
            {
                throw Error.ArgumentNull("querySettings");
            }

            if (assemblyProvider == null)
            {
                throw Error.ArgumentNull("assemblyProvider");
            }

            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            ApplyClause applyClause = ApplyClause;

            Contract.Assert(applyClause != null);

            // Ensure we have decided how to handle null propagation
            ODataQuerySettings updatedSettings = querySettings;

            if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default)
            {
                updatedSettings = new ODataQuerySettings(updatedSettings);
                updatedSettings.HandleNullPropagation = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query);
            }

            foreach (var transformation in applyClause.Transformations)
            {
                if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy)
                {
                    var binder = new AggregationBinder(updatedSettings, assemblyProvider, ResultClrType, Context.Model, transformation as TransformationNode, _serviceProvider);
                    query = binder.Bind(query);
                    this.ResultClrType = binder.ResultClrType;
                }
                else if (transformation.Kind == TransformationNodeKind.Filter)
                {
                    var        filterTransformation = transformation as FilterTransformationNode;
                    Expression filter = FilterBinder.Bind(filterTransformation.FilterClause, ResultClrType, Context.Model, assemblyProvider, updatedSettings, _serviceProvider);
                    query = ExpressionHelpers.Where(query, filter, ResultClrType);
                }
            }

            return(query);
        }
Beispiel #9
0
        private IQueryable AddOrderByQueryForProperty(IQueryable query, ODataQuerySettings querySettings,
                                                      OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered)
        {
            Context.UpdateQuerySettings(querySettings, query);

            LambdaExpression orderByExpression =
                FilterBinder.Bind(query, orderbyClause, Context.ElementClrType, Context.RequestContainer);

            querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType,
                                                   alreadyOrdered);
            return(querySoFar);
        }
Beispiel #10
0
        private void DoAggregatedGroupBy(string value, out IQueryable keys, out object[] aggragatedValues)
        {
            IQueryable                data;
            ODataQueryContext         context;
            ODataQuerySettings        settings;
            ApplyGroupbyClause        groupByClause;
            DefaultAssembliesResolver assembliesResolver;
            GroupByImplementation     groupByImplementation;
            Type keyType;
            IEnumerable <LambdaExpression> propertiesToGroupByExpressions;

            GetGroupByParams(value, out data, out context, out settings, out groupByClause, out assembliesResolver, out groupByImplementation, out keyType, out propertiesToGroupByExpressions);
            var propertyToAggregateExpression = FilterBinder.Bind(groupByClause.Aggregate.AggregatablePropertyExpression, context.ElementClrType, context.Model, assembliesResolver, settings);

            groupByImplementation.DoAggregatedGroupBy(data, 2000, groupByClause, keyType, propertiesToGroupByExpressions, propertyToAggregateExpression, out keys, out aggragatedValues);
        }
Beispiel #11
0
        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));
        }
Beispiel #12
0
        private static void GetGroupByParams(string value, out IQueryable data, out ODataQueryContext context, out ODataQuerySettings settings,
                                             out ApplyGroupbyClause groupByClause, out DefaultAssembliesResolver assembliesResolver,
                                             out GroupByImplementation groupByImplementation, out Type keyType, out IEnumerable <LambdaExpression> propertiesToGroupByExpressions)
        {
            string queryOption = "$apply";

            data     = TestDataSource.CreateData();
            settings = new ODataQuerySettings()
            {
                PageSize = 2000,
                HandleNullPropagation = HandleNullPropagationOption.False
            };
            var _settings = settings;

            var model = TestModelBuilder.CreateModel(new Type[] { typeof(Category), typeof(Product), typeof(Sales) });

            context = new ODataQueryContext(model, typeof(Sales),
                                            new ODataPath(new ODataPathSegment[] { new EntitySetPathSegment("Sales") }));
            var _context = context;

            IEdmNavigationSource source = model.FindDeclaredEntitySet("Sales");
            var parser = new ODataQueryOptionParser(model,
                                                    model.FindDeclaredType("System.Web.OData.Aggregation.Tests.Common.Sales"),
                                                    source,
                                                    new Dictionary <string, string>()
            {
                { queryOption, value }
            });

            var applyCaluse = parser.ParseApply();

            groupByClause      = applyCaluse.Transformations.First().Item2 as ApplyGroupbyClause;
            assembliesResolver = new DefaultAssembliesResolver();
            var _assembliesResolver = assembliesResolver;

            groupByImplementation = new GroupByImplementation()
            {
                Context = context
            };
            keyType = groupByImplementation.GetGroupByKeyType(groupByClause);
            var entityParam = Expression.Parameter(context.ElementClrType, "$it");

            propertiesToGroupByExpressions = groupByClause.SelectedPropertiesExpressions.Select(
                exp =>
                FilterBinder.Bind(exp, _context.ElementClrType, _context.Model, _assembliesResolver, _settings, entityParam));
        }
Beispiel #13
0
        private IQueryable AddOrderByQueryForProperty(
            IQueryable query, ODataQuerySettings querySettings, OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered)
        {
            // Ensure we have decided how to handle null propagation
            ODataQuerySettings updatedSettings = querySettings;

            if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default)
            {
                updatedSettings = new ODataQuerySettings(updatedSettings);
                updatedSettings.HandleNullPropagation =
                    HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query);
            }

            LambdaExpression orderByExpression =
                FilterBinder.Bind(orderbyClause, _contextElementClrType, Context.Model, updatedSettings);

            querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, _contextElementClrType, alreadyOrdered);
            return(querySoFar);
        }
Beispiel #14
0
        /// <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("query");
            }

            if (querySettings == null)
            {
                throw Error.ArgumentNull("querySettings");
            }

            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            ApplyClause applyClause = ApplyClause;

            Contract.Assert(applyClause != null);

            ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query);

            foreach (var transformation in applyClause.Transformations)
            {
                if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy)
                {
                    var binder = new AggregationBinder(updatedSettings, _assembliesResolver, ResultClrType, Context.Model, transformation);
                    query = binder.Bind(query);
                    this.ResultClrType = binder.ResultClrType;
                }
                else if (transformation.Kind == TransformationNodeKind.Filter)
                {
                    var        filterTransformation = transformation as FilterTransformationNode;
                    Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, Context.RequestContainer);
                    query = ExpressionHelpers.Where(query, filter, ResultClrType);
                }
            }

            return(query);
        }
Beispiel #15
0
        /// <summary>
        /// Parses a <paramref name="filter"/> clause on the given <paramref name="elementType"/>, binding
        /// the text into semantic nodes using the provided model.
        /// </summary>
        /// <param name="filter">String representation of the filter expression.</param>
        /// <param name="configuration">The configuration used for binding.</param>
        /// <param name="elementType">Type that the filter clause refers to.</param>
        /// <param name="navigationSource">Navigation source that the elements being filtered are from.</param>
        /// <returns>A <see cref="FilterClause"/> representing the metadata bound filter expression.</returns>
        private static FilterClause ParseFilterImplementation(string filter, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource)
        {
            ExceptionUtils.CheckArgumentNotNull(configuration, "configuration");
            ExceptionUtils.CheckArgumentNotNull(elementType, "elementType");
            ExceptionUtils.CheckArgumentNotNull(filter, "filter");

            // Get the syntactic representation of the filter expression
            UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveBuiltinIdentifier);
            QueryToken filterToken = expressionParser.ParseFilter(filter);

            // Bind it to metadata
            BindingState state = new BindingState(configuration);

            state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(elementType.ToTypeReference(), navigationSource);
            state.RangeVariables.Push(state.ImplicitRangeVariable);
            MetadataBinder binder       = new MetadataBinder(state);
            FilterBinder   filterBinder = new FilterBinder(binder.Bind, state);
            FilterClause   boundNode    = filterBinder.BindFilter(filterToken);

            return(boundNode);
        }
Beispiel #16
0
        /// <summary>
        /// Apply the filter 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>
        /// <param name="assemblyProvider">The <see cref="IAssemblyProvider"/> to use.</param>
        /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns>
        public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssemblyProvider assemblyProvider)
        {
            if (query == null)
            {
                throw Error.ArgumentNull("query");
            }

            if (assemblyProvider == null)
            {
                throw Error.ArgumentNull("assemblyProvider");
            }

            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            var filter = FilterBinder.Bind(FilterClause, Context.ElementClrType, Context.Model, assemblyProvider, querySettings);

            return(ExpressionHelpers.Where(query, filter, Context.ElementClrType));
        }
        /// <summary>
        /// Parses a <paramref name="query"/> clause on the given <paramref name="elementType"/>, binding
        /// the text into semantic nodes using the provided model.
        /// </summary>
        /// <param name="query">String representation of the filter expression.</param>
        /// <param name="configuration">The configuration used for binding.</param>
        /// <param name="elementType">Type that the filter clause refers to.</param>
        /// <param name="navigationSource">Navigation source that the elements being filtered are from.</param>
        /// <returns>A <see cref="FilterClause"/> representing the metadata bound filter expression.</returns>
        internal static ExpressionClause ParseExpressionImplementation(string query, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource)
        {
            ExceptionUtils.CheckArgumentNotNull(configuration, "configuration");
            ExceptionUtils.CheckArgumentNotNull(elementType, "elementType");
            ExceptionUtils.CheckArgumentNotNull(query, "query");

            // Get the syntactic representation of the filter expression
            UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit);
            QueryToken expressionToken = expressionParser.ParseFilter(query);

            // Bind it to metadata
            BindingState state = new BindingState(configuration);

            state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(elementType.ToTypeReference(), navigationSource);
            state.RangeVariables.Push(state.ImplicitRangeVariable);
            MetadataBinder   binder       = new MetadataBinder(state);
            FilterBinder     filterBinder = new FilterBinder(binder.Bind, state);
            ExpressionClause boundNode    = filterBinder.BindProperyExpression(expressionToken);

            return(boundNode);
        }
Beispiel #18
0
        private IQueryable AddOrderByQueryForProperty(IQueryable query, ODataQuerySettings querySettings,
                                                      OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered)
        {
            // TODO:
            //Context.UpdateQuerySettings(querySettings, query);

            var services = new ServiceCollection();

            services.AddSingleton <ODataQuerySettings>(querySettings);
            services.AddSingleton <IEdmModel>(Context.Model);
            services.AddSingleton <IAssemblyProvider>(_assemblyProvider);

            var requestContainer = services.BuildServiceProvider();
            LambdaExpression orderByExpression =
                //FilterBinder.Bind(orderbyClause, Context.ElementClrType, /*Context.RequestContainer*/ null);
                FilterBinder.Bind(orderbyClause, Context.ElementClrType, requestContainer);

            querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType,
                                                   alreadyOrdered);
            return(querySoFar);
        }
        /// <summary>
        /// Apply $filter parameter to query.
        /// </summary>
        /// <param name="query">
        /// The OData aware query.
        /// </param>
        /// <param name="filterText">
        /// The $filter parameter text.
        /// </param>
        /// <param name="entitySetName">
        /// The entity set name.
        /// </param>
        /// <typeparam name="T">
        /// The query type param
        /// </typeparam>
        /// <returns>
        /// The <see cref="ODataQuery{T}"/> query with applied filter parameter.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// Argument Null Exception
        /// </exception>
        public static ODataQuery <T> Filter <T>(this ODataQuery <T> query, string filterText, string entitySetName = null)
        {
            if (query == null)
            {
                throw new ArgumentNullException(nameof(query));
            }
            if (filterText == null)
            {
                throw new ArgumentNullException(nameof(filterText));
            }

            IEdmModel edmModel = query.EdmModel;

            ODataQueryOptionParser queryOptionParser = GetParser(
                query,
                entitySetName,
                new Dictionary <string, string> {
                { "$filter", filterText }
            });

            ODataSettings settings = query.ServiceProvider.GetRequiredService <ODataSettings>();

            FilterClause    filterClause     = queryOptionParser.ParseFilter();
            SingleValueNode filterExpression = filterClause.Expression.Accept(
                new ParameterAliasNodeTranslator(queryOptionParser.ParameterAliasNodes)) as SingleValueNode;

            filterExpression = filterExpression ?? new ConstantNode(null);
            filterClause     = new FilterClause(filterExpression, filterClause.RangeVariable);
            Contract.Assert(filterClause != null);

            var validator = new FilterQueryValidator(settings.DefaultQuerySettings);

            validator.Validate(filterClause, settings.ValidationSettings, edmModel);

            Expression filter = FilterBinder.Bind(query, filterClause, typeof(T), query.ServiceProvider);
            var        result = ExpressionHelpers.Where(query, filter, typeof(T));

            return(new ODataQuery <T>(result, query.ServiceProvider));
        }
Beispiel #20
0
        /// <summary>
        /// Apply the filter 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>
        /// <param name="assembliesResolver">The <see cref="IAssembliesResolver"/> to use.</param>
        /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns>
        public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssembliesResolver assembliesResolver)
        {
            if (query == null)
            {
                throw Error.ArgumentNull("query");
            }
            if (querySettings == null)
            {
                throw Error.ArgumentNull("querySettings");
            }
            if (assembliesResolver == null)
            {
                throw Error.ArgumentNull("assembliesResolver");
            }
            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            FilterClause filterClause = FilterClause;

            Contract.Assert(filterClause != null);

            // Ensure we have decided how to handle null propagation
            ODataQuerySettings updatedSettings = querySettings;

            if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default)
            {
                updatedSettings = new ODataQuerySettings(updatedSettings);
                updatedSettings.HandleNullPropagation = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query);
            }

            Expression filter = FilterBinder.Bind(filterClause, Context.ElementClrType, Context.Model, assembliesResolver, updatedSettings);

            query = ExpressionHelpers.Where(query, filter, Context.ElementClrType);
            return(query);
        }
Beispiel #21
0
 public ApplyBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state)
 {
     this.bindMethod   = bindMethod;
     this.state        = state;
     this.filterBinder = new FilterBinder(bindMethod, state);
 }
        internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType elementType, IEdmProperty property,
                                                                    Expression source, FilterClause filterClause)
        {
            Contract.Assert(elementType != null);
            Contract.Assert(property != null);
            Contract.Assert(source != null);

            IEdmEntityType declaringType = property.DeclaringType as IEdmEntityType;

            Contract.Assert(declaringType != null, "only entity types are projected.");

            // derived property using cast
            if (elementType != declaringType)
            {
                Type castType = EdmLibHelpers.GetClrType(declaringType, _model);
                if (castType == null)
                {
                    throw new ODataException(Error.Format(SRResources.MappingDoesNotContainEntityType,
                                                          declaringType.FullName()));
                }

                source = Expression.TypeAs(source, castType);
            }

            string     propertyName          = EdmLibHelpers.GetClrPropertyName(property, _model);
            Expression propertyValue         = Expression.Property(source, propertyName);
            Type       nullablePropertyType  = propertyValue.Type.ToNullable();
            Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue);

            if (filterClause != null && property.Type.IsCollection())
            {
                IEdmTypeReference edmElementType = property.Type.AsCollection().ElementType();
                Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model);
                if (clrElementType == null)
                {
                    throw new ODataException(Error.Format(SRResources.MappingDoesNotContainEntityType,
                                                          edmElementType.FullName()));
                }

                Expression filterSource =
                    typeof(IEnumerable).IsAssignableFrom(source.Type.GetProperty(propertyName).PropertyType)
                        ? Expression.Call(
                        ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(clrElementType),
                        nullablePropertyValue)
                        : nullablePropertyValue;

                Expression filterPredicate = FilterBinder.Bind(
                    filterClause,
                    clrElementType,
                    _model,
                    _assembliesResolver,
                    _settings);
                MethodCallExpression filterResult = Expression.Call(
                    ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(clrElementType),
                    filterSource,
                    filterPredicate);

                nullablePropertyType = filterResult.Type;
                if (_settings.HandleNullPropagation == HandleNullPropagationOption.True)
                {
                    // nullablePropertyValue == null ? null : filterResult
                    nullablePropertyValue = Expression.Condition(
                        test: Expression.Equal(nullablePropertyValue, Expression.Constant(value: null)),
                        ifTrue: Expression.Constant(value: null, type: nullablePropertyType),
                        ifFalse: filterResult);
                }
                else
                {
                    nullablePropertyValue = filterResult;
                }
            }

            if (_settings.HandleNullPropagation == HandleNullPropagationOption.True)
            {
                // source == null ? null : propertyValue
                propertyValue = Expression.Condition(
                    test: Expression.Equal(source, Expression.Constant(value: null)),
                    ifTrue: Expression.Constant(value: null, type: nullablePropertyType),
                    ifFalse: nullablePropertyValue);
            }
            else
            {
                // need to cast this to nullable as EF would fail while materializing if the property is not nullable and source is null.
                propertyValue = nullablePropertyValue;
            }

            return(propertyValue);
        }
Beispiel #23
0
        /// <summary>
        /// Generate an expand item based on an ExpandTermToken
        /// </summary>
        /// <param name="tokenIn">the expandTerm token to visit</param>
        /// <returns>the expand item for this expand term token.</returns>
        private ExpandedNavigationSelectItem GenerateExpandItem(ExpandTermToken tokenIn)
        {
            ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn");

            // ensure that we're always dealing with a normalized tree
            if (tokenIn.PathToNavProp.NextToken != null && !tokenIn.PathToNavProp.IsNamespaceOrContainerQualified())
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingANonNormalizedTree);
            }

            PathSegmentToken        currentToken           = tokenIn.PathToNavProp;
            IEdmEntityType          currentLevelEntityType = this.entityType;
            List <ODataPathSegment> pathSoFar         = new List <ODataPathSegment>();
            PathSegmentToken        firstNonTypeToken = currentToken;

            if (currentToken.IsNamespaceOrContainerQualified())
            {
                pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, ref currentLevelEntityType, out firstNonTypeToken));
            }

            IEdmProperty edmProperty = currentLevelEntityType.FindProperty(firstNonTypeToken.Identifier);

            if (edmProperty == null)
            {
                throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentLevelEntityType.FullName(), currentToken.Identifier));
            }

            IEdmNavigationProperty currentNavProp = edmProperty as IEdmNavigationProperty;

            if (currentNavProp == null)
            {
                // the server allowed non-navigation, non-stream properties to be expanded, but then ignored them.
                if (this.Settings.UseWcfDataServicesServerBehavior && !edmProperty.Type.IsStream())
                {
                    return(null);
                }

                throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationProperty(currentToken.Identifier, currentLevelEntityType.FullName()));
            }

            pathSoFar.Add(new NavigationPropertySegment(currentNavProp, /*entitySet*/ null));
            ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar);

            SelectExpandClause subSelectExpand;

            if (tokenIn.ExpandOption != null)
            {
                subSelectExpand = this.GenerateSubExpand(currentNavProp, tokenIn);
            }
            else
            {
                subSelectExpand = BuildDefaultSubExpand();
            }

            subSelectExpand = this.DecorateExpandWithSelect(subSelectExpand, currentNavProp, tokenIn.SelectOption);

            IEdmEntitySet targetEntitySet = null;

            if (this.entitySet != null)
            {
                targetEntitySet = this.entitySet.FindNavigationTarget(currentNavProp);
            }

            // call MetadataBinder to build the filter clause
            FilterClause filterOption = null;

            if (tokenIn.FilterOption != null)
            {
                MetadataBinder binder       = this.BuildNewMetadataBinder(targetEntitySet);
                FilterBinder   filterBinder = new FilterBinder(binder.Bind, binder.BindingState);
                filterOption = filterBinder.BindFilter(tokenIn.FilterOption);
            }

            // call MetadataBinder again to build the orderby clause
            OrderByClause orderbyOption = null;

            if (tokenIn.OrderByOption != null)
            {
                MetadataBinder binder        = this.BuildNewMetadataBinder(targetEntitySet);
                OrderByBinder  orderByBinder = new OrderByBinder(binder.Bind);
                orderbyOption = orderByBinder.BindOrderBy(binder.BindingState, new OrderByToken[] { tokenIn.OrderByOption });
            }

            return(new ExpandedNavigationSelectItem(pathToNavProp, targetEntitySet, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.InlineCountOption, subSelectExpand));
        }
Beispiel #24
0
        /// <summary>
        /// Execute 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>
        /// <param name="assembliesResolver">IAssembliesResolver provided by the framework.</param>
        /// <param name="aggregationWindowSize">The max number of results to aggregate in each aggregation batch</param>
        /// <returns>The new <see cref="IQueryable"/> After the apply query has been applied to.</returns>
        public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssembliesResolver assembliesResolver, int aggregationWindowSize)
        {
            if (query == null)
            {
                throw Error.ArgumentNull("query");
            }
            if (querySettings == null)
            {
                throw Error.ArgumentNull("querySettings");
            }
            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            ApplyClause applyClause = ApplyClause;

            Contract.Assert(applyClause != null);

            // Ensure we have decided how to handle null propagation
            ODataQuerySettings updatedSettings = querySettings;

            if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default)
            {
                updatedSettings = new ODataQuerySettings(updatedSettings);
                updatedSettings.HandleNullPropagation = HandleNullPropagationOption.False;
            }

            var maxResults = querySettings.PageSize ?? 2000;

            if (aggregationWindowSize != 0)
            {
                maxResults = (aggregationWindowSize < MAX_AGGREGATION_WINDOW_SIZE)
                    ? aggregationWindowSize
                    : MAX_AGGREGATION_WINDOW_SIZE;
            }

            // Call InterceptingProvider.Intercept that will create a new <see cref="InterceptingProvider"/> and set its visitors.
            // InterceptingProvider will wrap the actual IQueryable to implement unsupported operations in memory.
            var        mi      = _intercept_mi.MakeGenericMethod(this.Context.ElementClrType);
            IQueryable results = mi.Invoke(null, new object[] { query, maxResults, null }) as IQueryable;

            // Each transformation is the input for the next transformation in the transformations list
            foreach (var transformation in applyClause.Transformations)
            {
                switch (transformation.Item1)
                {
                case "aggregate":
                    ApplyAggregateClause aggregateClause = transformation.Item2 as ApplyAggregateClause;
                    if (aggregateClause == null)
                    {
                        throw Error.Argument("aggregation transformation type mismatch", transformation.Item2);
                    }

                    LambdaExpression propertyToAggregateExpression = FilterBinder.Bind(aggregateClause.AggregatablePropertyExpression, Context.ElementClrType, Context.Model, assembliesResolver, updatedSettings);

                    var aggregationImplementation = AggregationMethodsImplementations.GetAggregationImplementation(aggregateClause.AggregationMethod);
                    if (results.Provider is InterceptingProvider)
                    {
                        (results.Provider as InterceptingProvider).Combiner =
                            aggregationImplementation.CombineTemporaryResults;
                    }

                    IQueryable queryToUse = results;
                    if (aggregateClause.AggregatableProperty.Contains('/'))
                    {
                        queryToUse = AggregationImplementationBase.FilterNullValues(query, this.Context.ElementClrType, aggregateClause);
                    }

                    var      projectionLambda  = AggregationImplementationBase.GetProjectionLambda(this.Context.ElementClrType, aggregateClause, propertyToAggregateExpression);
                    string[] aggregationParams = AggregationImplementationBase.GetAggregationParams(aggregateClause.AggregationMethod);
                    var      aggragationResult = aggregationImplementation.DoAggregatinon(this.Context.ElementClrType, queryToUse, aggregateClause, projectionLambda, aggregationParams);
                    var      aliasType         = aggregationImplementation.GetResultType(this.Context.ElementClrType, aggregateClause);

                    results = this.ProjectResult(aggragationResult, aggregateClause.Alias, aliasType);
                    Context = new ODataQueryContext(this.Context.Model, results.ElementType);

                    break;

                case "groupby":
                    IEnumerable <LambdaExpression> propertiesToGroupByExpressions = null;
                    var groupByImplementation = new GroupByImplementation()
                    {
                        Context = this.Context
                    };
                    var groupByClause = transformation.Item2 as ApplyGroupbyClause;
                    if (groupByClause == null)
                    {
                        throw Error.Argument("aggregation transformation type mismatch", transformation.Item2);
                    }

                    var entityParam = Expression.Parameter(this.Context.ElementClrType, "$it");
                    if (groupByClause.SelectedPropertiesExpressions != null)
                    {
                        propertiesToGroupByExpressions =
                            groupByClause.SelectedPropertiesExpressions.Select(
                                exp =>
                                FilterBinder.Bind(
                                    exp, this.Context.ElementClrType, this.Context.Model, assembliesResolver, updatedSettings, entityParam));
                    }

                    var keyType = groupByImplementation.GetGroupByKeyType(groupByClause);
                    if (groupByClause.Aggregate == null)
                    {
                        // simple group-by without aggregation method
                        results = groupByImplementation.DoGroupBy(results, maxResults, groupByClause, keyType, propertiesToGroupByExpressions);
                    }
                    else
                    {
                        IQueryable keys = null;
                        propertyToAggregateExpression = FilterBinder.Bind(groupByClause.Aggregate.AggregatablePropertyExpression, Context.ElementClrType, Context.Model, assembliesResolver, updatedSettings);

                        object[] aggragatedValues = null;
                        groupByImplementation.DoAggregatedGroupBy(results, maxResults, groupByClause, keyType, propertiesToGroupByExpressions, propertyToAggregateExpression,
                                                                  out keys, out aggragatedValues);

                        results = ProjectGroupedResult(groupByClause, keys, aggragatedValues, keyType, Context);
                    }

                    Context = new ODataQueryContext(this.Context.Model, results.ElementType);
                    break;

                case "filter":
                    var filterClause = transformation.Item2 as ApplyFilterClause;
                    if (filterClause == null)
                    {
                        throw Error.Argument("aggregation transformation type mismatch", transformation.Item2);
                    }

                    var filterImplementation = new FilterImplementation()
                    {
                        Context = this.Context
                    };
                    results = filterImplementation.DoFilter(results, filterClause, querySettings, this._queryOptionParser);

                    break;

                default:
                    throw Error.NotSupported("aggregation not supported", transformation.Item1);
                }
            }

            object convertedResult = null;

            return(results);
        }
Beispiel #25
0
        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 <IEdmProperty> propertiesSoFar = new HashSet <IEdmProperty>();
            bool orderByItSeen = false;

            foreach (OrderByNode node in nodes)
            {
                OrderByPropertyNode propertyNode = node as OrderByPropertyNode;

                if (propertyNode != null)
                {
                    IEdmProperty     property  = propertyNode.Property;
                    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(property))
                    {
                        throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, property.Name));
                    }
                    propertiesSoFar.Add(property);

                    if (propertyNode.OrderByClause != null)
                    {
                        // Ensure we have decided how to handle null propagation
                        ODataQuerySettings updatedSettings = querySettings;
                        if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default)
                        {
                            updatedSettings = new ODataQuerySettings(updatedSettings);
                            updatedSettings.HandleNullPropagation = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query);
                        }

                        LambdaExpression orderByExpression =
                            FilterBinder.Bind(propertyNode.OrderByClause, Context.ElementClrType, Context.Model, updatedSettings);
                        querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType, alreadyOrdered);
                    }
                    else
                    {
                        querySoFar = ExpressionHelpers.OrderByProperty(querySoFar, property, direction, Context.ElementClrType, 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);
        }
        /// <summary>
        /// Core logic for applying the query option to the IQueryable.
        /// </summary>
        /// <param name="query">The original <see cref="IQueryable"/>.</param>
        /// <param name="querySettings">Query setting used for validating the query option.</param>
        /// <param name="orderByNodes">OrderBy information required to correctly apply the query option for default implementation.</param>
        /// <param name="context">The <see cref="ODataQueryContext"/> which contains the <see cref="IEdmModel"/> and some type information</param>
        /// <param name="skipTokenRawValue">The raw string value of the skiptoken query parameter.</param>
        /// <returns></returns>
        private static IQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings, IList <OrderByNode> orderByNodes, ODataQueryContext context, string skipTokenRawValue)
        {
            if (context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            IDictionary <string, OrderByDirection> directionMap;

            if (orderByNodes != null)
            {
                directionMap =
                    orderByNodes.OfType <OrderByPropertyNode>().ToDictionary(node => node.Property.Name, node => node.Direction);
            }
            else
            {
                directionMap = new Dictionary <string, OrderByDirection>();
            }

            IDictionary <string, object> propertyValuePairs = PopulatePropertyValuePairs(skipTokenRawValue, context);

            if (propertyValuePairs.Count == 0)
            {
                throw Error.InvalidOperation("Unable to get property values from the skiptoken value.");
            }

            ExpressionBinderBase binder = new FilterBinder(context.RequestContainer);
            bool parameterizeConstant   = querySettings.EnableConstantParameterization;
            ParameterExpression param   = Expression.Parameter(context.ElementClrType);

            Expression where = null;

            /* We will create a where lambda of the following form -
             * Where (Prop1>Value1)
             * OR (Prop1=Value1 AND Prop2>Value2)
             * OR (Prop1=Value1 AND Prop2=Value2 AND Prop3>Value3)
             * and so on...
             * Adding the first true to simplify implementation.
             */
            Expression lastEquality  = null;
            bool       firstProperty = true;

            foreach (KeyValuePair <string, object> item in propertyValuePairs)
            {
                string           key      = item.Key;
                MemberExpression property = Expression.Property(param, key);
                object           value    = item.Value;

                Expression     compare   = null;
                ODataEnumValue enumValue = value as ODataEnumValue;
                if (enumValue != null)
                {
                    value = enumValue.Value;
                }

                Expression constant = parameterizeConstant ? LinqParameterContainer.Parameterize(value.GetType(), value) : Expression.Constant(value);
                if (directionMap.ContainsKey(key) && directionMap[key] == OrderByDirection.Descending)
                {
                    compare = binder.CreateBinaryExpression(BinaryOperatorKind.LessThan, property, constant, true);
                }
                else
                {
                    compare = binder.CreateBinaryExpression(BinaryOperatorKind.GreaterThan, property, constant, true);
                }

                if (firstProperty)
                {
                    lastEquality  = binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true);
                    where         = compare;
                    firstProperty = false;
                }
                else
                {
                    Expression condition = Expression.AndAlso(lastEquality, compare);
                    where        = Expression.OrElse(where, condition);
                    lastEquality = Expression.AndAlso(lastEquality, binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true));
                }
            }

            Expression whereLambda = Expression.Lambda(where, param);

            return(ExpressionHelpers.Where(query, whereLambda, query.ElementType));
        }
Beispiel #27
0
        internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType elementType, IEdmProperty property,
                                                                    Expression source, FilterClause filterClause)
        {
            Contract.Assert(elementType != null);
            Contract.Assert(property != null);
            Contract.Assert(source != null);

            IEdmEntityType declaringType = property.DeclaringType as IEdmEntityType;

            Contract.Assert(declaringType != null, "only entity types are projected.");

            // derived property using cast
            if (elementType != declaringType)
            {
                Type castType = EdmLibHelpers.GetClrType(declaringType, _model);
                if (castType == null)
                {
                    throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType,
                                                          declaringType.FullName()));
                }

                source = Expression.TypeAs(source, castType);
            }

            string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model);

            Expression propertyValue = ExpressionBinderBase.GetPropertyExpression(source, propertyName);

            Type       nullablePropertyType  = propertyValue.Type.ToNullable();
            Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue);

            if (filterClause != null)
            {
                var isCollection = property.Type.IsCollection();

                IEdmTypeReference edmElementType = (isCollection ? property.Type.AsCollection().ElementType() : property.Type);
                Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model);
                if (clrElementType == null)
                {
                    throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType,
                                                          edmElementType.FullName()));
                }

                Expression filterResult = nullablePropertyValue;

                if (isCollection)
                {
                    Expression filterSource =
                        typeof(IEnumerable).IsAssignableFrom(source.Type.GetProperty(propertyName).PropertyType)
                            ? Expression.Call(
                            ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(clrElementType),
                            nullablePropertyValue)
                            : nullablePropertyValue;

                    // TODO: Implement proper support for $select/$expand after $apply
                    Expression filterPredicate = FilterBinder.Bind(null, filterClause, clrElementType, _context.RequestContainer);
                    filterResult = Expression.Call(
                        ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(clrElementType),
                        filterSource,
                        filterPredicate);

                    nullablePropertyType = filterResult.Type;
                }
                else if (_settings.HandleReferenceNavigationPropertyExpandFilter)
                {
                    var filterLambdaExpression = FilterBinder.Bind(null, filterClause, clrElementType, _context.RequestContainer) as LambdaExpression;
                    if (filterLambdaExpression == null)
                    {
                        throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression,
                                                              property.Name, nameof(LambdaExpression)));
                    }

                    var filterParameter     = filterLambdaExpression.Parameters.First();
                    var predicateExpression = new ReferenceNavigationPropertyExpandFilterVisitor(filterParameter, nullablePropertyValue).Visit(filterLambdaExpression.Body);

                    // predicateExpression == true ? nullablePropertyValue : null
                    filterResult = Expression.Condition(
                        test: predicateExpression,
                        ifTrue: nullablePropertyValue,
                        ifFalse: Expression.Constant(value: null, type: nullablePropertyType));
                }

                if (_settings.HandleNullPropagation == HandleNullPropagationOption.True)
                {
                    // nullablePropertyValue == null ? null : filterResult
                    nullablePropertyValue = Expression.Condition(
                        test: Expression.Equal(nullablePropertyValue, Expression.Constant(value: null)),
                        ifTrue: Expression.Constant(value: null, type: nullablePropertyType),
                        ifFalse: filterResult);
                }
                else
                {
                    nullablePropertyValue = filterResult;
                }
            }

            if (_settings.HandleNullPropagation == HandleNullPropagationOption.True)
            {
                // source == null ? null : propertyValue
                propertyValue = Expression.Condition(
                    test: Expression.Equal(source, Expression.Constant(value: null)),
                    ifTrue: Expression.Constant(value: null, type: nullablePropertyType),
                    ifFalse: nullablePropertyValue);
            }
            else
            {
                // need to cast this to nullable as EF would fail while materializing if the property is not nullable and source is null.
                propertyValue = nullablePropertyValue;
            }

            return(propertyValue);
        }