コード例 #1
0
        public void CanFilterPagedDataWithPartialMatch()
        {
            // Arrange
            var items           = MockModel.ArrangeFixture();
            var pagedSize       = 5;
            var expectedResults = items.Count - 1;

            var settings = new PagedDataSettings()
            {
                TotalPerPage = pagedSize
            };

            settings.Filter.Add(new FilterSettings()
            {
                Property     = "Label",
                Value        = "Label",
                IsExactMatch = false
            });

            // Act
            var result = _dataPager.GetPagedData(items.AsQueryable(), settings);

            // Assert
            Assert.True(result.TotalRecords == expectedResults, "Results differ of what they should be. Possibly the filter on the DataPager class is not working.");
            Assert.True(result.Result.Count == pagedSize, "Total items in first paged batch does not match the configured page size.");
        }
コード例 #2
0
        public void CanFilterAndSortPostQueryPathsByDescending()
        {
            // Arrange
            var items     = MockModel.ArrangeFixture();
            var pagedSize = 5;

            var settings = new PagedDataSettings()
            {
                TotalPerPage = pagedSize
            };

            settings.Filter.Add(new FilterSettings()
            {
                Property = "ChildCollection.Label",
                Value    = "First"
            });

            settings.Sorting.Add(new SortingSettings()
            {
                PostQuerySortingPath = "ChildCollection.Label",
                Order = SortOrderEnum.DESC
            });

            // Act
            var result = _dataPager.GetPagedData(items.AsQueryable(), settings);

            // Assert
            Assert.True(result.Result[0].ChildCollection[0].Label.Contains("Third"));
            Assert.True(result.Result[0].ChildCollection[1].Label.Contains("Second"));
            Assert.True(result.Result[0].ChildCollection[2].Label.Contains("First"));
        }
コード例 #3
0
        public void CanFilterPagedDataWithExactMatch()
        {
            // Arrange
            var items           = MockModel.ArrangeFixture();
            var pagedSize       = 5;
            var expectedResults = 1;

            var settings = new PagedDataSettings()
            {
                TotalPerPage = pagedSize
            };

            settings.Filter.Add(new FilterSettings()
            {
                Property     = "Label",
                Value        = items.Last().Label,
                IsExactMatch = true
            });

            // Act
            var result = _dataPager.GetPagedData(items.AsQueryable(), settings);

            // Assert
            Assert.True(result.TotalRecords == expectedResults, "Results differ of what they should be. Possibly the filter on the DataPager class is not working.");
            Assert.True(result.Result.Count == expectedResults, "Returned results differ than total records.");
        }
コード例 #4
0
        public void CanFilterPostQueryPathsByExactMatch()
        {
            // Arrange
            var items     = MockModel.ArrangeFixture();
            var pagedSize = 5;

            var settings = new PagedDataSettings()
            {
                TotalPerPage = pagedSize
            };

            settings.Filter.Add(new FilterSettings()
            {
                Property     = "ChildCollection.Label",
                Value        = "First",
                IsExactMatch = true
            });

            // Act
            var result = _dataPager.GetPagedData(items.AsQueryable(), settings);

            // Assert
            Assert.True(result.TotalRecords == 0);
            Assert.True(result.Result.Count() == 0);
        }
コード例 #5
0
 /// <summary>
 /// Merges two filters into one final query.
 /// </summary>
 /// <param name="query">The IQueryable instance to be parsed.</param>
 /// <returns>The string lambda expression.</returns>
 private Expression <Func <Entity, bool> > MergeFilters(PagedDataSettings settings,
                                                        Expression <Func <Entity, bool> > expressionLeft,
                                                        Expression <Func <Entity, bool> > expressionRight,
                                                        bool isAllSearch = false)
 {
     if (expressionLeft == null && expressionRight == null)
     {
         return(x => 1 == 1);
     }
     else if (expressionLeft != null && expressionRight != null)
     {
         if (isAllSearch)
         {
             return(PredicateBuilder.Or(expressionLeft, expressionRight));
         }
         else
         {
             return(PredicateBuilder.And(expressionLeft, expressionRight));
         }
     }
     else if (expressionLeft == null)
     {
         return(expressionRight);
     }
     else
     {
         return(expressionLeft);
     }
 }
コード例 #6
0
        /// <summary>
        /// Filters the final paged result AFTER the projection was executed in database by adapters.
        /// </summary>
        /// <remarks>
        /// This method is useful for children collection filter. The only way to accomplish through LINQ.
        /// </remarks>
        private IList PostQueryCallbacksInvoker(IList fetchedResult, PagedDataSettings settings)
        {
            fetchedResult = this.PostQueryFilter(fetchedResult, settings);
            fetchedResult = this.PostQuerySort(fetchedResult, settings);

            return(fetchedResult);
        }
コード例 #7
0
        /// <summary>
        /// Applies in memory sorting to IList.
        /// </summary>
        /// <param name="fetchedResult">This is the return from EF query after going to DB.</param>
        /// <param name="settings">Paged data source settings.</param>
        /// <returns>Sorted collection result.</returns>
        private IList PostQuerySort(IList fetchedResult, PagedDataSettings settings)
        {
            // Generates the order clause based on supplied parameters
            if (settings.Sorting != null && settings.Sorting.Count > 0)
            {
                var validOrderSettings = settings.Sorting.Where(x => !String.IsNullOrEmpty(x.PostQuerySortingPath)).GroupBy(x => x.Property).Select(y => y.FirstOrDefault());

                foreach (var o in validOrderSettings)
                {
                    foreach (var result in fetchedResult)
                    {
                        // Only supports if it is immediately the first level. We checked this above =)
                        var navigationPropertyCollection = o.PostQuerySortingPath.Split('.')[0];

                        int collectionPathTotal = 0;
                        var propInfo            = result.GetNestedPropInfo(navigationPropertyCollection, out collectionPathTotal);

                        // Apparently String implements IEnumerable, since it is a collection of chars
                        if (propInfo != null && (propInfo.PropertyType != typeof(string) || typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType)))
                        {
                            // Gets the property reference
                            var collectionProp = result.GetPropValue(navigationPropertyCollection);

                            result.RearrangeCollectionInstance(navigationPropertyCollection, o.PostQuerySortingPath.Substring(navigationPropertyCollection.Length + 1) + " " + o.Order.ToString());
                        }
                    }
                }
            }

            return(fetchedResult);
        }
コード例 #8
0
        /// <summary>
        /// Returns a collection of data results that can be paged.
        /// </summary>
        /// <param name="queryableToPage">IQueryable instance of <see cref="Entity"/> which will act as data source for the pagination.</param>
        /// <param name="settings">Settings for the search.</param>
        /// <param name="PreConditionsToPagedDataFilter">Pre condition Expression Filters.</param>
        /// <param name="ExtraPagedDataFilter">Extra conditions Expression Filters to be applied along with settings filters.</param>
        /// <returns>Filled PageData results instance.</returns>
        public IPagedDataResult <Entity> GetPagedData(IQueryable <Entity> queryableToPage,
                                                      PagedDataSettings settings,
                                                      Expression <Func <Entity, bool> > PreConditionsToPagedDataFilter = null,
                                                      Expression <Func <Entity, bool> > ExtraPagedDataFilter           = null)
        {
            try
            {
                IQueryable <Entity> pagedDataQuery = queryableToPage;

                // Applies pre conditioning filtering to the data source. (This is a pre-filter that executes before the filters instructed by PagedDataSettings).
                if (PreConditionsToPagedDataFilter != null)
                {
                    pagedDataQuery = pagedDataQuery.Where(PreConditionsToPagedDataFilter);
                }

                // Adds composed filter to the query here (This is the default filter inspector bult-in for the search).
                // This is a merge result from default query engine + customized queries from devs (ExtraPagedDataFilter method).
                pagedDataQuery = pagedDataQuery.AsExpandable().Where(MergeFilters(settings, DefaultPagedDataFilter(settings), ExtraPagedDataFilter, settings.SearchInALL));

                // Adds sorting capabilities
                pagedDataQuery = this.AddSorting(pagedDataQuery, settings);

                // Total number of records regardless of paging.
                var totalRecordsInDB = pagedDataQuery.AsExpandable().Count();

                // Shapes final result model. Post query filters to inner collection data are applied at this moment.
                return(pagedDataQuery.Skip((settings.Page - 1) * settings.TotalPerPage).Take(settings.TotalPerPage).AsExpandable().BuildUpResult(totalRecordsInDB, (p) => PostQueryCallbacksInvoker(p, settings)));
            }
            catch (Exception ex)
            {
                throw new Exception($"There was an error paging the datasource for entity: {nameof(Entity)}. Exception Details: {ex.Message}");
            }
        }
コード例 #9
0
        public void MustTranformPagedDataAttributesWhenSorting()
        {
            // Arrange
            var items = MockModel.ArrangeFixture();

            var settings = new PagedDataSettings()
            {
            };

            settings.Sorting.Add(new SortingSettings()
            {
                Property = "UIFirstProperty"
            });

            settings.Sorting.Add(new SortingSettings()
            {
                Property = "UISecondProperty"
            });

            // Act
            var transformedSettings = DataPagerAdapter.TransformSettings(settings, this.GetType().GetMethod(nameof(MustTranformPagedDataAttributesWhenSorting)));

            // Assert
            Assert.Equal(transformedSettings.Filter.Count, settings.Filter.Count);
            Assert.Equal(transformedSettings.Sorting.Count, settings.Sorting.Count);
            Assert.True(transformedSettings.Sorting.Where(x => x.Property == "BackEndFirstProperty").Any());
            Assert.True(transformedSettings.Sorting.Where(x => x.Property == "BackEndSecondProperty").Any());
        }
コード例 #10
0
        public void MustTranformPagedDataAttributesWhenFiltering()
        {
            // Arrange
            var items = MockModel.ArrangeFixture();

            var settings = new PagedDataSettings()
            {
            };

            settings.Filter.Add(new FilterSettings()
            {
                Property     = "UIFirstProperty",
                Value        = items.First().Label,
                IsExactMatch = true
            });

            settings.Filter.Add(new FilterSettings()
            {
                Property     = "UISecondProperty",
                Value        = items.Last().Label,
                IsExactMatch = true
            });

            // Act
            var transformedSettings = DataPagerAdapter.TransformSettings(settings, this.GetType().GetMethod(nameof(MustTranformPagedDataAttributesWhenFiltering)));

            // Assert
            Assert.Equal(transformedSettings.Filter.Count, settings.Filter.Count);
            Assert.Equal(transformedSettings.Sorting.Count, settings.Sorting.Count);
            Assert.True(transformedSettings.Filter.Where(x => x.Property == "BackEndFirstProperty").Any());
            Assert.True(transformedSettings.Filter.Where(x => x.Property == "BackEndSecondProperty").Any());
            Assert.True(transformedSettings.Filter.Where(x => x.Property == "BackEndFirstProperty").FirstOrDefault().Value.Contains("First"));
            Assert.True(transformedSettings.Filter.Where(x => x.Property == "BackEndSecondProperty").FirstOrDefault().Value.Contains("Nineth"));
        }
コード例 #11
0
        /// <summary>
        /// Filters paged data based on <see cref="PagedDataSourceSettings"/>.
        /// </summary>
        /// <param name="settings">Custom settings to be dynamically converted and apply as filters to the result set.</param>
        /// <returns>
        /// Filtered result set from MongoDB.
        /// </returns>
        /// <remarks>
        /// This method was overriden here because MongoDB driver is not mature enough as EntityFramework,
        /// so some filter settings from the default <see cref="QueryableDataSourcePager{Key, Entity}"/> where throwing runtime exceptions.
        /// </remarks>
        protected override Expression <Func <Entity, bool> > DefaultPagedDataFilter(PagedDataSettings settings)
        {
            bool firstExecution = true;
            var  queryLinq      = string.Empty;
            // Holds Parameters values per index of this list (@0, @1, @2, etc).
            var paramValues = new List <object>();

            if (settings.Filter != null && settings.Filter.Count > 0)
            {
                var validFilterSettings = settings.Filter.Where(x => !String.IsNullOrEmpty(x.Property) && !String.IsNullOrEmpty(x.Value)).GroupBy(x => x.Property).Select(y => y.FirstOrDefault());

                foreach (var pFilter in validFilterSettings)
                {
                    int    collectionPathTotal   = 0;
                    var    propInfo              = this.GetValidatedPropertyInfo(pFilter.Property, out collectionPathTotal);
                    string nullableValueOperator = "";

                    // Apparently String implements IEnumerable, since it is a collection of chars
                    if (propInfo != null && (propInfo.PropertyType == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType)))
                    {
                        if (Nullable.GetUnderlyingType(propInfo.PropertyType) != null)
                        {
                            nullableValueOperator = ".Value";
                        }

                        if (collectionPathTotal == 0)
                        {
                            if (propInfo.PropertyType == typeof(string))
                            {
                                // Applying filter to nullable entity's property.
                                if (pFilter.IsExactMatch)
                                {
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + pFilter.Property + nullableValueOperator + ".ToUpper() == @" + paramValues.Count;
                                }
                                else
                                {
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + pFilter.Property + nullableValueOperator + ".ToUpper().Contains(@" + paramValues.Count + ")";
                                }

                                paramValues.Add(pFilter.Value.ToUpper());
                                firstExecution = false;
                            }
                            else
                            {
                                throw new NotImplementedException($"{nameof(DataPagerMongoDB<Key, Entity>)} only supports string properties filtering at the moment.");
                            }
                        }
                        else
                        {
                            throw new NotImplementedException($"Inner collection filtering is not supported by {nameof(DataPagerMongoDB<Key, Entity>)}");
                        }
                    }
                }
            }

            // Returns current default query as expression.
            return(queryLinq.ParseLambda <Entity>(paramValues.ToArray()));
        }
コード例 #12
0
        public void ShouldSetDefaultSortingByAttributes()
        {
            // Arrange
            var items = MockModel.ArrangeFixture();

            var settings = new PagedDataSettings()
            {
            };

            // Act
            var transformedSettings = DataPagerAdapter.TransformSettings(settings, this.GetType().GetMethod(nameof(ShouldSetDefaultSortingByAttributes)));

            // Assert
            Assert.Equal(transformedSettings.Sorting.Count, 1);
            Assert.True(transformedSettings.Sorting.Where(x => x.Property == "MyDefaultSortingProperty").Any());
        }
コード例 #13
0
        /// <summary>
        /// This method translates a <see cref="PagedDataSettings"/> payload to another configured instance based on
        /// any <see cref="PagedDataFilterAttribute"/> or <see cref="PagedDataDefaultSortingAttribute"/>
        /// that may have been applied to a controller method.
        /// </summary>
        /// <param name="settings">
        /// Payload received by the controller serialized by the ASP.NET default serializer or anyother type
        /// of configuration you may use.
        /// </param>
        /// <param name="callingMethodInfo">
        /// The method base information that contains all the attributes in order to perform the adapter operation.
        /// </param>
        /// <remarks>
        /// This method needs to be called directly in the Controller method.
        /// It won't work if called inside another method called by the controller.
        /// It needs to be right after the controller method's execution call stack.
        /// </remarks>
        /// <returns>Updated/Adapted settings that are ready to be supplied to the <see cref="DataPager{Key, Entity}"/> class.</returns>
        public static PagedDataSettings TransformSettings(PagedDataSettings settings, MethodBase callingMethodInfo)
        {
            // TODO: Not yet supported on .NET Core, refer to this: https://github.com/dotnet/corefx/issues/1797
            // Gets previous calling method information to get whatever attribute that may have been applied.
            //StackTrace stackTrace = new StackTrace();
            //MethodBase methodBase = stackTrace.GetFrame(1).GetMethod();

            var props   = callingMethodInfo.GetCustomAttributes <PagedDataFilterAttribute>();
            var sorting = callingMethodInfo.GetCustomAttribute <PagedDataDefaultSortingAttribute>();

            // Checks if the consumer of the search wants to filter in ALL fields.
            if (settings.Filter.Where(x => x.Property.ToUpper() == PagedDataSettings.FILTERALLIDENTIFIER).Any())
            {
                // Overriding all possible conjunctions set as OR since this is a "ALL" search.
                foreach (var filter in settings.Filter)
                {
                    filter.Conjunction = LogicalConjunctionEnum.AND;
                }

                TranslateALLSearchFilters(props.Where(x => x.IgnoreOnSearch == false), settings);
            }

            TranslateDefaultFilters(props.Where(x => x.IgnoreOnSearch == false), settings);

            foreach (var property in props)
            {
                if (settings.Sorting != null)
                {
                    // Searchs replacements for sort queries.
                    var item = settings.Sorting.Where(x => x.Property == property.MapsFrom).FirstOrDefault();
                    if (item != null)
                    {
                        item.Property = property.MapsTo;

                        // Only gets the first item if it is piped for sorting.
                        // TODO: Implement piped sorting for future releases.
                        item.PostQuerySortingPath = property.PostQueryFilterPath.Split('|').First();
                    }
                }
            }

            TranslateDefaultSorting(sorting, settings);

            return(settings);
        }
コード例 #14
0
        public void CanGetPagedData()
        {
            // Arrange
            var items     = MockModel.ArrangeFixture();
            var pagedSize = 5;

            var settings = new PagedDataSettings()
            {
                TotalPerPage = pagedSize
            };

            // Act
            var result = _dataPager.GetPagedData(items.AsQueryable(), settings);

            // Assert
            Assert.True(result.TotalRecords == items.Count, "Total records in the paged result does not match the total in the fixture collection.");
            Assert.True(result.Result.Count == pagedSize, "Total items in first paged batch does not match the configured page size.");
        }
コード例 #15
0
        /// <summary>
        /// Adapts data that was sent by any consumer (Can be API or UI) to a DB persisted model.
        /// </summary>
        /// <param name="callerClassType">
        /// This is the caller class type. Just use "this" as a reference.
        /// </param>
        /// <param name="callingMethodName">
        /// This is the method of the calling name. Use it as "nameof(MyMethod)".
        /// </param>
        /// <param name="settings">
        /// The initial settings sent by any consumer of page data functionality.
        /// </param>
        /// <returns>
        /// Returns a new settings model that can be used to filter DB model entities.
        /// </returns>
        /// <example>
        /// PagedDataSettings.TransformSettings(this, nameof(MyMethod), MySettingsPayload);
        /// </example>
        public static PagedDataSettings TransformSettings(Type callerClassType, string callingMethodName, PagedDataSettings settings)
        {
            var transformedSettings = new PagedDataSettings(); // We take the input and transform it back to the user.

            // Gets previous calling method data with the decorated attributes.
            MethodBase methodBase = callerClassType.GetMethod(callingMethodName);

            var props   = methodBase.GetCustomAttributes <PagedDataAdapterAttribute>();
            var sorting = methodBase.GetCustomAttribute <PagedDataDefaultSortingAttribute>();

            // Checks if caller wants to filter in ALL fields.
            if (settings.Filter.Where(x => x.Property.ToUpper() == PagedDataSettings.FILTERALLIDENTIFIER).Any())
            {
                // Overriding all possible conjunctions set as OR since this is an "ALL" search.
                foreach (var filter in settings.Filter)
                {
                    filter.Conjunction = LogicalConjunctionEnum.AND;
                }

                InspectForAllFilter(props, settings, transformedSettings);
            }
            else
            {
                // TODO: Implement "OR" in client side / consumer if needed/request by someone.
                if (settings.Filter.Where(x => x.Conjunction != LogicalConjunctionEnum.AND).Any())
                {
                    throw new NotImplementedException($"'Or' filtering not supported by: {nameof(PagedDataAdapter)} transformation class.");
                }

                InspectFilterSettings(props, settings, transformedSettings);
            }

            // Copies (or adds default) sorting configurations.
            InspectSorting(props, sorting, settings, transformedSettings);

            return(settings);
        }
コード例 #16
0
        public void CanSortPagedDataByDescending()
        {
            // Arrange
            var items     = MockModel.ArrangeFixture();
            var pagedSize = 5;

            var settings = new PagedDataSettings()
            {
                TotalPerPage = pagedSize
            };

            settings.Sorting.Add(new SortingSettings()
            {
                Property = "Label",
                Order    = SortOrderEnum.DESC // Should default to ASC
            });

            // Act
            var result = _dataPager.GetPagedData(items.AsQueryable(), settings);

            // Assert
            Assert.True(result.Result.First().Label == "Third Label");
            Assert.True(result.Result.Last().Label == "Nineth");
        }
コード例 #17
0
        /// <summary>
        /// Adds default filter mechanism to GetPagedData method.
        /// </summary>
        /// <remarks>
        /// This method allows multi-navigation property filter as long as they are not collections.
        /// It also supports collection BUT the collection needs to be the immediate first level of navigation property, and you can't use more than one depth.
        /// </remarks>
        /// <param name="settings">Current filter settings supplied by the consumer.</param>
        /// <returns>Expression to be embedded to the IQueryable filter instance.</returns>
        protected virtual Expression <Func <Entity, bool> > DefaultPagedDataFilter(PagedDataSettings settings)
        {
            bool hasErrors      = false; // Identifies if one of the parameters had wrong data
            bool firstExecution = true;
            var  queryLinq      = string.Empty;
            // Holds Parameters values per index of this list (@0, @1, @2, etc).
            var paramValues = new List <object>();

            if (settings.Filter != null && settings.Filter.Count > 0)
            {
                var validFilterSettings = settings.Filter.Where(x => !String.IsNullOrEmpty(x.Property) && !String.IsNullOrEmpty(x.Value)).GroupBy(x => x.Property).Select(y => y.FirstOrDefault());

                foreach (var pFilter in validFilterSettings)
                {
                    int    collectionPathTotal   = 0;
                    var    propInfo              = this.GetValidatedPropertyInfo(pFilter.Property, out collectionPathTotal);
                    string nullableValueOperator = string.Empty;

                    // Apparently String implements IEnumerable, since it is a collection of chars
                    if (propInfo != null && (propInfo.PropertyType == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType)))
                    {
                        if (Nullable.GetUnderlyingType(propInfo.PropertyType) != null)
                        {
                            nullableValueOperator = ".Value";
                        }

                        if (collectionPathTotal == 0)
                        {
                            if (propInfo.PropertyType.IsAssignableFrom(typeof(DateTime)))
                            {
                                // Applies filter do DateTime properties
                                DateTime castedDateTime;
                                if (DateTime.TryParseExact(pFilter.Value, UI_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out castedDateTime))
                                {
                                    // Successfully casted the value to a datetime.
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + "DbFunctions.TruncateTime(" + pFilter.Property + nullableValueOperator + ") == @" + paramValues.Count;
                                    paramValues.Add(castedDateTime.Date);
                                    firstExecution = false;
                                }
                                else
                                {
                                    hasErrors = true;
                                    break;
                                }
                            }
                            else
                            {
                                // Applying filter to nullable entity's property.
                                if (pFilter.IsExactMatch)
                                {
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + pFilter.Property + nullableValueOperator + ".ToString().ToUpper() == @" + paramValues.Count;
                                }
                                else
                                {
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + pFilter.Property + nullableValueOperator + ".ToString().ToUpper().Contains(@" + paramValues.Count + ")";
                                }

                                paramValues.Add(pFilter.Value.ToUpper());
                                firstExecution = false;
                            }
                        }
                        else
                        {
                            // Only supports if it is immediately the first level. We checked this above =)
                            var navigationPropertyCollection = pFilter.Property.Split('.')[0];

                            // Sub collection filter LINQ
                            // Applies filter do DateTime properties
                            if (propInfo.PropertyType.IsAssignableFrom(typeof(DateTime)))
                            {
                                DateTime castedDateTime;
                                if (DateTime.TryParseExact(pFilter.Value, UI_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out castedDateTime))
                                {
                                    // Successfully casted the value to a datetime.
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + navigationPropertyCollection + ".Where(DbFunctions.TruncateTime(" + pFilter.Property.Remove(0, navigationPropertyCollection.Length + 1) + nullableValueOperator + ") == @" + paramValues.Count + ").Count() > 0";
                                    paramValues.Add(castedDateTime.Date);
                                    firstExecution = false;
                                }
                                else
                                {
                                    hasErrors = true;
                                    break;
                                }
                            }
                            else
                            {
                                if (pFilter.IsExactMatch)
                                {
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + navigationPropertyCollection + ".Where(" + pFilter.Property.Remove(0, navigationPropertyCollection.Length + 1) + nullableValueOperator + ".ToString().ToUpper() == @" + paramValues.Count + ").Count() > 0";
                                }
                                else
                                {
                                    queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + navigationPropertyCollection + ".Where(" + pFilter.Property.Remove(0, navigationPropertyCollection.Length + 1) + nullableValueOperator + ".ToString().ToUpper().Contains(@" + paramValues.Count + ")).Count() > 0";
                                }

                                paramValues.Add(pFilter.Value.ToUpper());
                                firstExecution = false;
                            }
                        }
                    }
                }
            }

            // Returns current default query as expression.
            if (!hasErrors || settings.SearchInALL)
            {
                return(queryLinq.ParseLambda <Entity>(paramValues.ToArray()));
            }
            else
            {
                return("1 != 1".ParseLambda <Entity>(null)); //Invalidates the result set
            }
        }
コード例 #18
0
        /// <summary>
        /// Inspects if "ALL" option should be applied. This will gather all attributes decorated in the method and shape possible filter results.
        /// </summary>
        private static void InspectForAllFilter(IEnumerable <PagedDataAdapterAttribute> props, PagedDataSettings initialSettings, PagedDataSettings transformedSettings)
        {
            bool firstRun = true;

            initialSettings.SearchInALL = true;

            // Saves all added filters which are not ALL. This may be extra filters that are not mapped directly to DB Entities.
            var otherFilters = initialSettings.Filter.Where(x => x.Property.ToUpper() != PagedDataSettings.FILTERALLIDENTIFIER).ToList();

            // Gets the value in "All" search if supplied.
            var filterValue = initialSettings.Filter.Where(x => x.Property.ToUpper() == PagedDataSettings.FILTERALLIDENTIFIER).FirstOrDefault().Value;

            // Inspecting each property sent by the consumer of Paged Data.
            foreach (var property in props)
            {
                var attrValue = property.MapsTo;

                transformedSettings.Filter.Add(new FilterSettings()
                {
                    PostQueryFilterPath = property.InMemoryPath,
                    Property            = property.MapsTo,
                    IsExactMatch        = false,
                    Value       = filterValue ?? string.Empty,
                    Conjunction = firstRun ? LogicalConjunctionEnum.AND : LogicalConjunctionEnum.OR
                });

                firstRun = false;
            }

            // Removes all duplicates from filters that could be mapped. We will leave just filters that were not applied here.
            otherFilters.RemoveAll(x => transformedSettings.Filter.Where(y => y.Property == x.Property).Any());

            // After we copied all filters that could be mapped by the adapter decorators, we are also pushing the ones that could not (Since they can be extra filters).
            transformedSettings.Filter = transformedSettings.Filter.Concat(otherFilters).ToList();
        }
コード例 #19
0
        /// <summary>
        /// Applies filter to inner collections of a query result set from database.
        /// This is applied as a memory LINQ To Objects filter.
        /// </summary>
        /// <param name="fetchedResult">This is the return from EF query after going to DB.</param>
        /// <param name="settings">Paged data source settings.</param>
        /// <returns>Filtered collection result.</returns>
        private IList PostQueryFilter(IList fetchedResult, PagedDataSettings settings)
        {
            if (settings.Filter != null && settings.Filter.Count > 0 && !settings.SearchInALL)
            {
                var validFilterSettings = settings.Filter
                                          .Where(x => !String.IsNullOrEmpty(x.Value) && !String.IsNullOrEmpty(x.PostQueryFilterPath))
                                          .GroupBy(x => x.Property)
                                          .Select(y => y.FirstOrDefault());

                if (validFilterSettings.Count() > 0)
                {
                    foreach (var result in fetchedResult)
                    {
                        string bufferedNavigationProperty = string.Empty; // TODO: Check if this needs to be refactored.
                        bool   firstExecution             = true;         // Identifies if it is the first filter added in order to apply or not conjunctions.
                        var    queryLinq   = string.Empty;                // Final lambda query to be applied to resulting data source which is already Linq-To-Objects
                        var    paramValues = new List <object>();         // Holds Parameters values per index of this list (@0, @1, @2, etc).

                        foreach (var pFilter in validFilterSettings)
                        {
                            // This allows piping for DTO in memory filtering paths.
                            var    pipes      = pFilter.PostQueryFilterPath.Split('|');
                            bool   piped      = false; // Set this to true in the end if we run more than one pipe.
                            string pipedQuery = "";

                            foreach (var pipe in pipes)
                            {
                                // Only supports if it is immediately the first level. We checked this above =)
                                var navigationPropertyCollection = pipe.Split('.')[0];

                                // We are buffering the query, but if the property has changed, then we will execute and replace the value inMemory and move on
                                if (!firstExecution && bufferedNavigationProperty != navigationPropertyCollection)
                                {
                                    result.ReplaceCollectionInstance(bufferedNavigationProperty, queryLinq);

                                    // Assign brand new values to move on as a new. Resetting here since seems the collection property CHANGED.
                                    queryLinq      = string.Empty;
                                    firstExecution = true;
                                    pipedQuery     = string.Empty;
                                }

                                bufferedNavigationProperty = navigationPropertyCollection;
                                int collectionPathTotal = 0;
                                var propInfo            = result.GetNestedPropInfo(navigationPropertyCollection, out collectionPathTotal);

                                // Tests if the current property to be filtered is NULLABLE in order to add ".Value" to string lambda query.
                                var nullableValueOperator = string.Empty;
                                var pipeProperty          = pipe.Remove(0, navigationPropertyCollection.Length + 1); // Clean pipe without collection prefix.

                                // Apparently String implements IEnumerable, since it is a collection of chars
                                if (propInfo != null && (propInfo.PropertyType != typeof(string) || typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType)))
                                {
                                    // Nullable subproperty field.
                                    if (Nullable.GetUnderlyingType(result.GetNestedPropInfo(pipe).PropertyType) != null)
                                    {
                                        nullableValueOperator = ".Value";
                                    }

                                    // Sub collection filter LINQ
                                    // Applies filter do DateTime properties
                                    if (result.GetNestedPropInfo(pipe).PropertyType.IsAssignableFrom(typeof(DateTime)))
                                    {
                                        // Applies filter do DateTime properties
                                        DateTime castedDateTime;
                                        if (DateTime.TryParseExact(pFilter.Value, UI_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out castedDateTime))
                                        {
                                            // Successfully casted the value to a datetime.
                                            queryLinq += (!piped ? string.Empty : " OR ") + pipeProperty + nullableValueOperator + ".Date == @" + paramValues.Count;

                                            paramValues.Add(castedDateTime.Date);
                                        }
                                    }
                                    else
                                    {
                                        // Sub collection filter LINQ
                                        if (pFilter.IsExactMatch)
                                        {
                                            pipedQuery += (!piped ? string.Empty : " OR ") + pipeProperty + nullableValueOperator + ".ToString().ToUpper() == \"" + pFilter.Value.ToUpper() + "\"";
                                        }
                                        else
                                        {
                                            pipedQuery += (!piped ? string.Empty : " OR ") + pipeProperty + nullableValueOperator + ".ToString().ToUpper().Contains(\"" + pFilter.Value.ToUpper() + "\")";
                                        }
                                    }
                                }

                                piped = true;
                            }

                            if (!String.IsNullOrEmpty(pipedQuery))
                            {
                                queryLinq += (firstExecution ? string.Empty : " " + pFilter.Conjunction + " ") + "(" + pipedQuery + ")";
                            }

                            firstExecution = false;
                        }

                        result.ReplaceCollectionInstance(bufferedNavigationProperty, queryLinq);
                    }
                }
            }

            return(fetchedResult);
        }
コード例 #20
0
 private static void TranslateDefaultFilters(IEnumerable <PagedDataFilterAttribute> props, PagedDataSettings settings)
 {
     // If it can find, then do the job. Otherwise we will fallback to whatever the UI sends directly to IQueryable. (No security issue since this is just for filter/ordering.
     foreach (var property in props)
     {
         // Translates a prom from => to relationship.
         var filterProp = settings.Filter.Where(x => x.Property.Equals(property.MapsFrom)).FirstOrDefault();
         if (filterProp != null)
         {
             filterProp.Property            = property.MapsTo;
             filterProp.PostQueryFilterPath = String.IsNullOrEmpty(property.PostQueryFilterPathExplict) ? property.PostQueryFilterPath : property.PostQueryFilterPathExplict;
         }
     }
 }
コード例 #21
0
 /// <summary>
 /// Returns a collection of data results that can be paged.
 /// </summary>
 /// <param name="settings">Settings for the search.</param>
 /// <returns>Filled PagedData instance.</returns>
 public IPagedDataResult <Entity> GetPagedData(PagedDataSettings settings)
 {
     return(_dataSourcePager.GetPagedData(GetQueryable(), settings, AddPreConditionsPagedDataFilter(settings), AddExtraPagedDataFilter(settings)));
 }
コード例 #22
0
        private static void TranslateDefaultSorting(PagedDataDefaultSortingAttribute sortingAttribute, PagedDataSettings settings)
        {
            if (settings.Sorting == null || settings.Sorting.Count == 0)
            {
                if (settings.Sorting == null)
                {
                    settings.Sorting = new List <SortingSettings>();
                }

                if (sortingAttribute != null)
                {
                    settings.Sorting.Add(new SortingSettings()
                    {
                        Property = sortingAttribute.Property,
                        Order    = sortingAttribute.IsAscending ? SortOrderEnum.ASC : SortOrderEnum.DESC
                    });
                }
            }
        }
コード例 #23
0
        /// <summary>
        /// Configures settins for sorting if specified by user, or adds default sorting field that must be specified.
        /// </summary>
        private static void InspectSorting(IEnumerable <PagedDataAdapterAttribute> props, PagedDataDefaultSortingAttribute sortingAttribute, PagedDataSettings settings, PagedDataSettings transformedSettings)
        {
            foreach (var property in props)
            {
                if (settings.Sorting != null)
                {
                    // Searchs filter configurations sent by consumer.
                    var item = settings.Sorting.Where(x => x.Property == property.PropFrom).FirstOrDefault();

                    if (item != null)
                    {
                        transformedSettings.Sorting.Add(
                            new SortingSettings()
                        {
                            Property             = property.MapsTo,
                            PostQuerySortingPath = property.InMemoryPath.Split('|').First() // Only gets the first if it is piped for sorting.
                        });
                    }
                }
            }

            // Only applies this default sorting if consumer specified none.
            if (transformedSettings.Sorting == null || transformedSettings.Sorting.Count == 0)
            {
                if (sortingAttribute != null)
                {
                    transformedSettings.Sorting.Add(new SortingSettings()
                    {
                        Property = sortingAttribute.Property,
                        Order    = sortingAttribute.IsAscending ? SortOrderEnum.ASC : SortOrderEnum.DESC
                    });
                }
                else
                {
                    throw new MissingMemberException("No sorting was specified by the consumer and no default sorting property was configured among the adapters therefore it is not possible to excecute the paged data query.");
                }
            }
        }
コード例 #24
0
 /// <summary>
 /// Adds extra filter to PagedData method.
 /// </summary>
 /// <remarks>
 /// Override this method in <see cref="Repository{Key, Entity}{Key, Entity}"/> implementation
 /// if you want to add custom filter to your paged data source.
 /// </remarks>
 /// <param name="settings">Current filter settings supplied by the consumer.</param>
 /// <returns>Expression to be embedded to the IQueryable filter instance.</returns>
 protected virtual Expression <Func <Entity, bool> > AddExtraPagedDataFilter(PagedDataSettings settings)
 {
     // Needs to be overriden by devs to add behavior to this.
     // Change the injected filter on concrete repositories.
     return(null);
 }
コード例 #25
0
        private static void TranslateALLSearchFilters(IEnumerable <PagedDataFilterAttribute> props, PagedDataSettings settings)
        {
            bool firstExecution = true;

            settings.SearchInALL = true;

            // Saves all previous filters (Some cases we need it, as in TabView case).
            var otherFilters = settings.Filter.Where(x => x.Property.ToUpper() != PagedDataSettings.FILTERALLIDENTIFIER).ToList();

            // Gets "All" supplied value
            var filterValue = settings.Filter.Where(x => x.Property.ToUpper() == PagedDataSettings.FILTERALLIDENTIFIER).FirstOrDefault().Value;

            // Resets all prior filter
            settings.Filter.Clear();

            foreach (var property in props)
            {
                var attrValue = property.MapsTo;

                settings.Filter.Add(new FilterSettings()
                {
                    PostQueryFilterPath = String.IsNullOrEmpty(property.PostQueryFilterPathExplict) ? property.PostQueryFilterPath : property.PostQueryFilterPathExplict,
                    Property            = property.MapsTo,
                    IsExactMatch        = false,
                    Value       = filterValue ?? string.Empty,
                    Conjunction = firstExecution ? LogicalConjunctionEnum.AND : LogicalConjunctionEnum.OR
                });

                firstExecution = false;
            }

            // Removes all duplicates from pre existing filters.
            otherFilters.RemoveAll(x => settings.Filter.Where(y => y.Property == x.Property).Any());

            // Adds any other existing filters.
            settings.Filter = settings.Filter.Concat(otherFilters).ToList();
        }
コード例 #26
0
 /// <summary>
 /// Returns a paged, filtered and sorted collection.
 /// </summary>
 /// <param name="settings">Settings model for the search.</param>
 /// <returns>Collection of filtered items result.</returns>
 public IPagedDataResult <Entity> GetPagedData(PagedDataSettings settings)
 {
     return(_dataPager.GetPagedData(Collection.AsQueryable(), settings, this.AddPreConditionsPagedDataFilter(settings), this.AddExtraPagedDataFilter(settings)));
 }
コード例 #27
0
 /// <summary>
 /// Returns a collection of data results that can be paged.
 /// </summary>
 /// <param name="settings">Settings for the search.</param>
 /// <returns>Filled PagedData instance.</returns>
 public IPagedDataResult <Entity> GetPagedData(PagedDataSettings settings)
 {
     return(_dataSourcePager.GetPagedData((IQueryable <Entity>) this.List(), settings, this.AddPreConditionsPagedDataFilter(settings), this.AddExtraPagedDataFilter(settings)));
 }
コード例 #28
0
        /// <summary>
        /// Adds default sorting mechanism to GetPagedData method.
        /// </summary>
        /// <remarks>
        /// This method allows multi-navigation property filter as long as they are not collections.
        /// It also supports collection BUT the collection needs to be the immediate first level of navigation property, and you can't use more than one depth.
        ///
        /// - The input IQueryable is being returned. Seems if you try to apply changes by reference, you don't get it outside of this method. May be implicit LINQ behavior.
        /// </remarks>
        /// <param name="settings">Current sorting settings supplied by the consumer.</param>
        /// <returns>Expression to be embedded to the IQueryable instance.</returns>
        private IQueryable <Entity> AddSorting(IQueryable <Entity> pagedDataQuery, PagedDataSettings settings)
        {
            bool noFilterApplied = true;

            // Generates the order clause based on supplied parameters
            if (settings.Sorting != null && settings.Sorting.Count > 0)
            {
                var bufferedSortByClause = string.Empty;
                var validOrderSettings   = settings.Sorting.Where(x => !String.IsNullOrEmpty(x.Property) && String.IsNullOrEmpty(x.PostQuerySortingPath)).GroupBy(x => x.Property).Select(y => y.FirstOrDefault());

                foreach (var o in validOrderSettings)
                {
                    int collectionPathTotal = 0;
                    var propInfo            = this.GetValidatedPropertyInfo(o.Property, out collectionPathTotal);

                    // Apparently String implements IEnumerable, since it is a collection of chars
                    if (propInfo != null && (propInfo.PropertyType == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType)))
                    {
                        // Just applying DB filters to non collection related properties.
                        if (collectionPathTotal == 0)
                        {
                            if (noFilterApplied)
                            {
                                noFilterApplied = false;
                            }

                            bufferedSortByClause += o.Property + " " + o.Order.ToString() + ",";
                        }
                        else if (collectionPathTotal == 1)
                        {
                            // Deferring this to a post query sorting event as it can't be evaluated by a DB Query Graph Result.
                            if (string.IsNullOrEmpty(o.PostQuerySortingPath))
                            {
                                o.PostQuerySortingPath = o.Property;
                            }
                        }
                        else
                        {
                            // Only one level of deep collection supported at the moment.
                            throw new Exception($"The Advanced Search Engine only supports sorting in a one level nested collection path. Any dot notation path that contains more than one inner collection property is not supported.");
                        }
                    }
                }

                // Applies the buffered sort by
                if (!noFilterApplied)
                {
                    bufferedSortByClause = bufferedSortByClause.Substring(0, bufferedSortByClause.Length - 1); // Removes last comma
                    pagedDataQuery       = pagedDataQuery.OrderBy(bufferedSortByClause);
                }
            }

            // If there is no sorting configured, we need to add a default fallback one, which we will use the first property. Without this can't use LINQ Skip/Take
            if (settings.Sorting == null || settings.Sorting.Count == 0 || noFilterApplied)
            {
                var propCollection = typeof(Entity).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(x => !typeof(IEnumerable).IsAssignableFrom(x.PropertyType)).ToList();

                if (propCollection.Count() > 0)
                {
                    var firstFieldOfEntity = propCollection[0].Name;
                    pagedDataQuery = pagedDataQuery.OrderBy(firstFieldOfEntity + " " + SortOrderEnum.DESC.ToString());
                }
                else
                {
                    throw new Exception($"The supplied Entity {nameof(Entity)} has no public properties, therefore the method can't continue the sorting operation.");
                }
            }

            return(pagedDataQuery);
        }
コード例 #29
0
 /// <summary>
 /// Adds precondition global filters to paged data source.
 /// Rely on this if you want to add security filters.
 /// </summary>
 /// <remarks>
 /// Override this method in <see cref="Repository{Key, Entity}{Key, Entity}"/> implementation
 /// if you want to add pre conditions global filters to your paged data source.
 /// </remarks>
 /// <param name="settings">Current filter settings supplied by the consumer.</param>
 /// <returns>Expression to be embedded to the IQueryable filter instance.</returns>
 protected virtual Expression <Func <Entity, bool> > AddPreConditionsPagedDataFilter(PagedDataSettings settings)
 {
     // Needs to be overriden by devs to add behavior to this.
     return(null);
 }
コード例 #30
0
        private static void InspectFilterSettings(IEnumerable <PagedDataAdapterAttribute> props, PagedDataSettings settings, PagedDataSettings transformedSettings)
        {
            // If it can find, then do the job. Otherwise we will fallback to whatever the UI sends directly to IQueryable. (No security issue since this is just for filter/ordering.
            foreach (var property in props)
            {
                // Searchs for filters that were decorated.
                var filterProp = settings.Filter.Where(x => x.Property.Equals(property.PropFrom)).FirstOrDefault();

                if (filterProp != null)
                {
                    transformedSettings.Filter.Add(
                        new FilterSettings()
                    {
                        Property            = property.MapsTo,
                        PostQueryFilterPath = property.InMemoryPath
                    });
                }
            }
        }