This commit is contained in:
Biarity 2019-11-17 08:58:12 +10:00
commit 0dae8c8beb
9 changed files with 113 additions and 13 deletions

View File

@ -84,6 +84,15 @@ public class SieveCustomSortMethods : ISieveCustomSortMethods
return result; // Must return modified IQueryable<TEntity> return result; // Must return modified IQueryable<TEntity>
} }
public IQueryable<T> Oldest<T>(IQueryable<T> source, bool useThenBy, bool desc) where T : BaseEntity // Generic functions are allowed too
{
var result = useThenBy ?
((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
source.OrderByDescending(p => p.DateCreated);
return result;
}
} }
``` ```
And `SieveCustomFilterMethods`: And `SieveCustomFilterMethods`:
@ -97,6 +106,12 @@ public class SieveCustomFilterMethods : ISieveCustomFilterMethods
return result; // Must return modified IQueryable<TEntity> return result; // Must return modified IQueryable<TEntity>
} }
public IQueryable<T> Latest<T>(IQueryable<T> source, string op, string[] values) where T : BaseEntity // Generic functions are allowed too
{
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
return result;
}
} }
``` ```
@ -192,6 +207,7 @@ You can replace this DSL with your own (eg. use JSON instead) by implementing an
| `@=*` | Case-insensitive string Contains | | `@=*` | Case-insensitive string Contains |
| `_=*` | Case-insensitive string Starts with | | `_=*` | Case-insensitive string Starts with |
| `==*` | Case-insensitive string Equals | | `==*` | Case-insensitive string Equals |
| `!=*` | Case-insensitive string Not equals |
| `!@=*` | Case-insensitive string does not Contains | | `!@=*` | Case-insensitive string does not Contains |
| `!_=*` | Case-insensitive string does not Starts with | | `!_=*` | Case-insensitive string does not Starts with |

View File

@ -13,6 +13,7 @@ namespace Sieve.Models
private static readonly string[] Operators = new string[] { private static readonly string[] Operators = new string[] {
"!@=*", "!@=*",
"!_=*", "!_=*",
"!=*",
"!@=", "!@=",
"!_=", "!_=",
"==*", "==*",

View File

@ -187,6 +187,8 @@ namespace Sieve.Services
propertyValue = Expression.PropertyOrField(propertyValue, part); propertyValue = Expression.PropertyOrField(propertyValue, part);
} }
if (filterTerm.Values == null) continue;
foreach (var filterTermValue in filterTerm.Values) foreach (var filterTermValue in filterTerm.Values)
{ {
@ -335,10 +337,9 @@ namespace Sieve.Services
var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize; var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize;
var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize; var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize;
result = result.Skip((page - 1) * pageSize);
if (pageSize > 0) if (pageSize > 0)
{ {
result = result.Skip((page - 1) * pageSize);
result = result.Take(Math.Min(pageSize, maxPageSize)); result = result.Take(Math.Min(pageSize, maxPageSize));
} }
@ -387,6 +388,28 @@ namespace Sieve.Services
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance, _options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
typeof(IQueryable<TEntity>)); typeof(IQueryable<TEntity>));
if (customMethod == null)
{
// Find generic methods `public IQueryable<T> Filter<T>(IQueryable<T> source, ...)`
var genericCustomMethod = parent?.GetType()
.GetMethodExt(name,
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
typeof(IQueryable<>));
if (genericCustomMethod != null &&
genericCustomMethod.ReturnType.IsGenericType &&
genericCustomMethod.ReturnType.GetGenericTypeDefinition() == typeof(IQueryable<>))
{
var genericBaseType = genericCustomMethod.ReturnType.GenericTypeArguments[0];
var constraints = genericBaseType.GetGenericParameterConstraints();
if (constraints == null || constraints.Length == 0 || constraints.All((t) => t.IsAssignableFrom(typeof(TEntity))))
{
customMethod = genericCustomMethod.MakeGenericMethod(typeof(TEntity));
}
}
}
if (customMethod != null) if (customMethod != null)
{ {
try try

View File

@ -0,0 +1,13 @@
using System;
using Sieve.Attributes;
namespace SieveUnitTests.Entities
{
public class BaseEntity
{
public int Id { get; set; }
[Sieve(CanFilter = true, CanSort = true)]
public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;
}
}

View File

@ -3,13 +3,8 @@ using Sieve.Attributes;
namespace SieveUnitTests.Entities namespace SieveUnitTests.Entities
{ {
public class Comment public class Comment : BaseEntity
{ {
public int Id { get; set; }
[Sieve(CanFilter = true, CanSort = true)]
public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;
[Sieve(CanFilter = true)] [Sieve(CanFilter = true)]
public string Text { get; set; } public string Text { get; set; }
} }

View File

@ -3,9 +3,8 @@ using Sieve.Attributes;
namespace SieveUnitTests.Entities namespace SieveUnitTests.Entities
{ {
public class Post public class Post : BaseEntity
{ {
public int Id { get; set; }
[Sieve(CanFilter = true, CanSort = true)] [Sieve(CanFilter = true, CanSort = true)]
public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 8); public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 8);
@ -16,9 +15,6 @@ namespace SieveUnitTests.Entities
[Sieve(CanFilter = true, CanSort = true)] [Sieve(CanFilter = true, CanSort = true)]
public int CommentCount { get; set; } = new Random().Next(0, 1000); public int CommentCount { get; set; } = new Random().Next(0, 1000);
[Sieve(CanFilter = true, CanSort = true)]
public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;
[Sieve(CanFilter = true, CanSort = true)] [Sieve(CanFilter = true, CanSort = true)]
public int? CategoryId { get; set; } = new Random().Next(0, 4); public int? CategoryId { get; set; } = new Random().Next(0, 4);

View File

@ -96,6 +96,20 @@ namespace SieveUnitTests
Assert.IsTrue(result.Count() == 1); Assert.IsTrue(result.Count() == 1);
} }
[TestMethod]
public void NotEqualsCanBeCaseInsensitive()
{
var model = new SieveModel()
{
Filters = "Title!=*a"
};
var result = _processor.Apply(model, _posts);
Assert.AreEqual(result.First().Id, 1);
Assert.IsTrue(result.Count() == 3);
}
[TestMethod] [TestMethod]
public void ContainsIsCaseSensitive() public void ContainsIsCaseSensitive()
{ {
@ -193,6 +207,20 @@ namespace SieveUnitTests
Assert.IsTrue(result.Count() == 3); Assert.IsTrue(result.Count() == 3);
} }
[TestMethod]
public void CustomGenericFiltersWork()
{
var model = new SieveModel()
{
Filters = "Latest",
};
var result = _processor.Apply(model, _comments);
Assert.IsFalse(result.Any(p => p.Id == 0));
Assert.IsTrue(result.Count() == 2);
}
[TestMethod] [TestMethod]
public void CustomFiltersWithOperatorsWork() public void CustomFiltersWithOperatorsWork()
{ {
@ -272,6 +300,19 @@ namespace SieveUnitTests
Assert.IsFalse(result.First().Id == 0); Assert.IsFalse(result.First().Id == 0);
} }
[TestMethod]
public void CustomGenericSortsWork()
{
var model = new SieveModel()
{
Sorts = "Oldest",
};
var result = _processor.Apply(model, _posts);
Assert.IsTrue(result.Last().Id == 0);
}
[TestMethod] [TestMethod]
public void MethodNotFoundExceptionWork() public void MethodNotFoundExceptionWork()
{ {

View File

@ -32,5 +32,11 @@ namespace SieveUnitTests.Services
{ {
return source; return source;
} }
public IQueryable<T> Latest<T>(IQueryable<T> source, string op, string[] values) where T : BaseEntity
{
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
return result;
}
} }
} }

View File

@ -16,5 +16,14 @@ namespace SieveUnitTests.Services
return result; return result;
} }
public IQueryable<T> Oldest<T>(IQueryable<T> source, bool useThenBy, bool desc) where T : BaseEntity
{
var result = useThenBy ?
((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
source.OrderByDescending(p => p.DateCreated);
return result;
}
} }
} }