From 033a920bece123652c8f3d3af854db1428c79ba0 Mon Sep 17 00:00:00 2001 From: Biarity Date: Fri, 20 Apr 2018 19:27:04 +1000 Subject: [PATCH] Version 2.0 --- Sieve/Services/ISieveProcessor.cs | 11 +- Sieve/Services/SieveProcessor.cs | 323 +++++++++------------- SieveTests/Controllers/PostsController.cs | 2 +- SieveUnitTests/General.cs | 18 +- SieveUnitTests/Mapper.cs | 6 +- 5 files changed, 158 insertions(+), 202 deletions(-) diff --git a/Sieve/Services/ISieveProcessor.cs b/Sieve/Services/ISieveProcessor.cs index 81ec01b..6d02330 100644 --- a/Sieve/Services/ISieveProcessor.cs +++ b/Sieve/Services/ISieveProcessor.cs @@ -6,9 +6,12 @@ namespace Sieve.Services { public interface ISieveProcessor { - IQueryable ApplyAll(ISieveModel model, IQueryable source, object[] dataForCustomMethods = null); - IQueryable ApplySorting(ISieveModel model, IQueryable result, object[] dataForCustomMethods = null); - IQueryable ApplyFiltering(ISieveModel model, IQueryable result, object[] dataForCustomMethods = null); - IQueryable ApplyPagination(ISieveModel model, IQueryable result); + IQueryable Apply( + ISieveModel model, + IQueryable source, + object[] dataForCustomMethods = null, + bool applyFiltering = true, + bool applySorting = true, + bool applyPagination = true); } } \ No newline at end of file diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index ce1bbca..7930b2d 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -62,239 +62,192 @@ namespace Sieve.Services /// An instance of ISieveModel /// Data source /// Additional data that will be passed down to custom methods + /// Should the data be filtered? Defaults to true. + /// Should the data be sorted? Defaults to true. + /// Should the data be paginated? Defaults to true. /// Returns a transformed version of `source` - public IQueryable ApplyAll( + public IQueryable Apply( ISieveModel model, IQueryable source, - object[] dataForCustomMethods = null) + object[] dataForCustomMethods = null, + bool applyFiltering = true, + bool applySorting = true, + bool applyPagination = true) { var result = source; if (model == null) return result; - // Filter - result = ApplyFiltering(model, result, dataForCustomMethods); + try + { + // Filter + if (applyFiltering) + result = ApplyFiltering(model, result, dataForCustomMethods); - // Sort - result = ApplySorting(model, result, dataForCustomMethods); + // Sort + if (applySorting) + result = ApplySorting(model, result, dataForCustomMethods); - // Paginate - result = ApplyPagination(model, result); + // Paginate + if (applyPagination) + result = ApplyPagination(model, result); - return result; + return result; + } + catch (Exception ex) + { + if (_options.Value.ThrowExceptions) + { + if (ex is SieveException) + throw; + throw new SieveException(ex.Message, ex); + } + else + { + return result; + } + } } - /// - /// Apply filtering parameters found in `model` to `source` - /// - /// - /// An instance of ISieveModel - /// Data source - /// Additional data that will be passed down to custom methods - /// Returns a transformed version of `source` - public IQueryable ApplyFiltering( + private IQueryable ApplyFiltering( ISieveModel model, IQueryable result, object[] dataForCustomMethods = null) { - try - { - if (model?.FiltersParsed == null) - return result; - - foreach (var filterTerm in model.FiltersParsed) - { - var property = GetSieveProperty(false, true, filterTerm.Name); - - if (property != null) - { - var converter = TypeDescriptor.GetConverter(property.PropertyType); - var parameter = Expression.Parameter(typeof(TEntity), "e"); - - dynamic filterValue = Expression.Constant( - converter.CanConvertFrom(typeof(string)) ? - converter.ConvertFrom(filterTerm.Value) : - Convert.ChangeType(filterTerm.Value, property.PropertyType)); - - dynamic propertyValue = Expression.PropertyOrField(parameter, property.Name); - - if (filterTerm.OperatorIsCaseInsensitive) - { - propertyValue = Expression.Call(propertyValue, - typeof(string).GetMethods() - .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); - - filterValue = Expression.Call(filterValue, - typeof(string).GetMethods() - .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); - } - - Expression comparison; - - switch (filterTerm.OperatorParsed) - { - case FilterOperator.Equals: - comparison = Expression.Equal(propertyValue, filterValue); - break; - case FilterOperator.NotEquals: - comparison = Expression.NotEqual(propertyValue, filterValue); - break; - case FilterOperator.GreaterThan: - comparison = Expression.GreaterThan(propertyValue, filterValue); - break; - case FilterOperator.LessThan: - comparison = Expression.LessThan(propertyValue, filterValue); - break; - case FilterOperator.GreaterThanOrEqualTo: - comparison = Expression.GreaterThanOrEqual(propertyValue, filterValue); - break; - case FilterOperator.LessThanOrEqualTo: - comparison = Expression.LessThanOrEqual(propertyValue, filterValue); - break; - case FilterOperator.Contains: - comparison = Expression.Call(propertyValue, - typeof(string).GetMethods() - .First(m => m.Name == "Contains" && m.GetParameters().Length == 1), - filterValue); - break; - case FilterOperator.StartsWith: - comparison = Expression.Call(propertyValue, - typeof(string).GetMethods() - .First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1), - filterValue); break; - default: - comparison = Expression.Equal(propertyValue, filterValue); - break; - } - - result = result.Where(Expression.Lambda>( - comparison, - parameter)); - } - else - { - result = ApplyCustomMethod(result, filterTerm.Name, _customFilterMethods, - new object[] { - result, - filterTerm.Operator, - filterTerm.Value - }, dataForCustomMethods); - } - } - + if (model?.FiltersParsed == null) return result; - } - catch (Exception ex) + + foreach (var filterTerm in model.FiltersParsed) { - if (_options.Value.ThrowExceptions) + var property = GetSieveProperty(false, true, filterTerm.Name); + + if (property != null) { - if (ex is SieveException) - throw; - throw new SieveException(ex.Message, ex); + var converter = TypeDescriptor.GetConverter(property.PropertyType); + var parameter = Expression.Parameter(typeof(TEntity), "e"); + + dynamic filterValue = Expression.Constant( + converter.CanConvertFrom(typeof(string)) ? + converter.ConvertFrom(filterTerm.Value) : + Convert.ChangeType(filterTerm.Value, property.PropertyType)); + + dynamic propertyValue = Expression.PropertyOrField(parameter, property.Name); + + if (filterTerm.OperatorIsCaseInsensitive) + { + propertyValue = Expression.Call(propertyValue, + typeof(string).GetMethods() + .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); + + filterValue = Expression.Call(filterValue, + typeof(string).GetMethods() + .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); + } + + Expression comparison; + + switch (filterTerm.OperatorParsed) + { + case FilterOperator.Equals: + comparison = Expression.Equal(propertyValue, filterValue); + break; + case FilterOperator.NotEquals: + comparison = Expression.NotEqual(propertyValue, filterValue); + break; + case FilterOperator.GreaterThan: + comparison = Expression.GreaterThan(propertyValue, filterValue); + break; + case FilterOperator.LessThan: + comparison = Expression.LessThan(propertyValue, filterValue); + break; + case FilterOperator.GreaterThanOrEqualTo: + comparison = Expression.GreaterThanOrEqual(propertyValue, filterValue); + break; + case FilterOperator.LessThanOrEqualTo: + comparison = Expression.LessThanOrEqual(propertyValue, filterValue); + break; + case FilterOperator.Contains: + comparison = Expression.Call(propertyValue, + typeof(string).GetMethods() + .First(m => m.Name == "Contains" && m.GetParameters().Length == 1), + filterValue); + break; + case FilterOperator.StartsWith: + comparison = Expression.Call(propertyValue, + typeof(string).GetMethods() + .First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1), + filterValue); break; + default: + comparison = Expression.Equal(propertyValue, filterValue); + break; + } + + result = result.Where(Expression.Lambda>( + comparison, + parameter)); } else { - return result; + result = ApplyCustomMethod(result, filterTerm.Name, _customFilterMethods, + new object[] { + result, + filterTerm.Operator, + filterTerm.Value + }, dataForCustomMethods); } } + + return result; } - /// - /// Apply sorting parameters found in `model` to `source` - /// - /// - /// An instance of ISieveModel - /// Data source - /// Additional data that will be passed down to custom methods - /// Returns a transformed version of `source` - public IQueryable ApplySorting( + private IQueryable ApplySorting( ISieveModel model, IQueryable result, object[] dataForCustomMethods = null) { - try - { - if (model?.SortsParsed == null) - return result; - - var useThenBy = false; - foreach (var sortTerm in model.SortsParsed) - { - var property = GetSieveProperty(true, false, sortTerm.Name); - - if (property != null) - { - result = result.OrderByDynamic(property.Name, sortTerm.Descending, useThenBy); - } - else - { - result = ApplyCustomMethod(result, sortTerm.Name, _customSortMethods, - new object[] - { - result, - useThenBy, - sortTerm.Descending - }, dataForCustomMethods); - } - useThenBy = true; - } - + if (model?.SortsParsed == null) return result; - } - catch (Exception ex) + + var useThenBy = false; + foreach (var sortTerm in model.SortsParsed) { - if (_options.Value.ThrowExceptions) + var property = GetSieveProperty(true, false, sortTerm.Name); + + if (property != null) { - if (ex is SieveException) - throw; - throw new SieveException(ex.Message, ex); + result = result.OrderByDynamic(property.Name, sortTerm.Descending, useThenBy); } else { - return result; + result = ApplyCustomMethod(result, sortTerm.Name, _customSortMethods, + new object[] + { + result, + useThenBy, + sortTerm.Descending + }, dataForCustomMethods); } + useThenBy = true; } + + return result; } - /// - /// Apply pagination parameters found in `model` to `source` - /// - /// - /// An instance of ISieveModel - /// Data source - /// Additional data that will be passed down to custom methods - /// Returns a transformed version of `source` - public IQueryable ApplyPagination( + private IQueryable ApplyPagination( ISieveModel model, IQueryable result) { - try - { - var page = model?.Page ?? 1; - var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize; - var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize; + var page = model?.Page ?? 1; + var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize; + var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize; - result = result.Skip((page - 1) * pageSize); + result = result.Skip((page - 1) * pageSize); - if (pageSize > 0) - result = result.Take(Math.Min(pageSize, maxPageSize)); + if (pageSize > 0) + result = result.Take(Math.Min(pageSize, maxPageSize)); - return result; - } - catch (Exception ex) - { - if (_options.Value.ThrowExceptions) - { - if (ex is SieveException) - throw; - throw new SieveException(ex.Message, ex); - } - else - { - return result; - } - } + return result; } diff --git a/SieveTests/Controllers/PostsController.cs b/SieveTests/Controllers/PostsController.cs index 92cf8fb..c936985 100644 --- a/SieveTests/Controllers/PostsController.cs +++ b/SieveTests/Controllers/PostsController.cs @@ -28,7 +28,7 @@ namespace SieveTests.Controllers { var result = _dbContext.Posts.AsNoTracking(); - result = _sieveProcessor.ApplyAll(sieveModel, result); + result = _sieveProcessor.Apply(sieveModel, result); return Json(result.ToList()); } diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 1311934..ce6ad86 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -58,7 +58,7 @@ namespace SieveUnitTests Filters = "Title@=*a" }; - var result = _processor.ApplyFiltering(model, _posts); + var result = _processor.Apply(model, _posts); Assert.AreEqual(result.First().Id, 0); Assert.IsTrue(result.Count() == 1); @@ -72,7 +72,7 @@ namespace SieveUnitTests Filters = "Title@=a", }; - var result = _processor.ApplyFiltering(model, _posts); + var result = _processor.Apply(model, _posts); Assert.IsTrue(result.Count() == 0); } @@ -85,7 +85,7 @@ namespace SieveUnitTests Filters = "IsDraft==false" }; - var result = _processor.ApplyAll(model, _posts); + var result = _processor.Apply(model, _posts); Assert.IsTrue(result.Count() == 2); } @@ -99,7 +99,7 @@ namespace SieveUnitTests Sorts = "-IsDraft" }; - var result = _processor.ApplyAll(model, _posts); + var result = _processor.Apply(model, _posts); Assert.AreEqual(result.First().Id, 0); } @@ -116,7 +116,7 @@ namespace SieveUnitTests Console.WriteLine(model.FiltersParsed.First().Operator); Console.WriteLine(model.FiltersParsed.First().OperatorParsed); - var result = _processor.ApplyFiltering(model, _posts); + var result = _processor.Apply(model, _posts); @@ -132,7 +132,7 @@ namespace SieveUnitTests Filters = "Isnew", }; - var result = _processor.ApplyFiltering(model, _posts); + var result = _processor.Apply(model, _posts); Assert.IsFalse(result.Any(p => p.Id == 0)); Assert.IsTrue(result.Count() == 3); @@ -146,7 +146,7 @@ namespace SieveUnitTests Filters = "does not exist", }; - Assert.ThrowsException(() => _processor.ApplyFiltering(model, _posts)); + Assert.ThrowsException(() => _processor.Apply(model, _posts)); } [TestMethod] @@ -157,7 +157,7 @@ namespace SieveUnitTests Filters = "TestComment", }; - Assert.ThrowsException(() => _processor.ApplyFiltering(model, _posts)); + Assert.ThrowsException(() => _processor.Apply(model, _posts)); } [TestMethod] @@ -168,7 +168,7 @@ namespace SieveUnitTests Filters = "(Title|LikeCount)==3", }; - var result = _processor.ApplyFiltering(model, _posts); + var result = _processor.Apply(model, _posts); Assert.AreEqual(result.First().Id, 3); Assert.IsTrue(result.Count() == 1); diff --git a/SieveUnitTests/Mapper.cs b/SieveUnitTests/Mapper.cs index f97be42..ecb04b5 100644 --- a/SieveUnitTests/Mapper.cs +++ b/SieveUnitTests/Mapper.cs @@ -53,7 +53,7 @@ namespace SieveUnitTests Filters = "shortname@=A", }; - var result = _processor.ApplyAll(model, _posts); + var result = _processor.Apply(model, _posts); Assert.AreEqual(result.First().ThisHasNoAttributeButIsAccessible, "A"); @@ -69,9 +69,9 @@ namespace SieveUnitTests Sorts = "OnlySortableViaFluentApi" }; - var result = _processor.ApplySorting(model, _posts); + var result = _processor.Apply(model, _posts, applyFiltering: false, applyPagination: false); - Assert.ThrowsException(() => _processor.ApplyAll(model, _posts)); + Assert.ThrowsException(() => _processor.Apply(model, _posts)); Assert.AreEqual(result.First().Id, 3);