mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-21 21:12:50 +01:00
Merge pull request #98 from hasanmanzak/master
OrderByDynamic is modified to be able to handle inherited members...
This commit is contained in:
commit
803055029e
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Sieve.Extensions
|
namespace Sieve.Extensions
|
||||||
{
|
{
|
||||||
@ -9,10 +10,11 @@ namespace Sieve.Extensions
|
|||||||
public static IQueryable<TEntity> OrderByDynamic<TEntity>(
|
public static IQueryable<TEntity> OrderByDynamic<TEntity>(
|
||||||
this IQueryable<TEntity> source,
|
this IQueryable<TEntity> source,
|
||||||
string fullPropertyName,
|
string fullPropertyName,
|
||||||
|
PropertyInfo propertyInfo,
|
||||||
bool desc,
|
bool desc,
|
||||||
bool useThenBy)
|
bool useThenBy)
|
||||||
{
|
{
|
||||||
var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName);
|
var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName, propertyInfo);
|
||||||
|
|
||||||
var command = desc
|
var command = desc
|
||||||
? (useThenBy ? "ThenByDescending" : "OrderByDescending")
|
? (useThenBy ? "ThenByDescending" : "OrderByDescending")
|
||||||
@ -28,15 +30,27 @@ namespace Sieve.Extensions
|
|||||||
return source.Provider.CreateQuery<TEntity>(resultExpression);
|
return source.Provider.CreateQuery<TEntity>(resultExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Expression<Func<TEntity, object>> GenerateLambdaWithSafeMemberAccess<TEntity>(string fullPropertyName)
|
private static Expression<Func<TEntity, object>> GenerateLambdaWithSafeMemberAccess<TEntity>
|
||||||
|
(
|
||||||
|
string fullPropertyName,
|
||||||
|
PropertyInfo propertyInfo
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var parameter = Expression.Parameter(typeof(TEntity), "e");
|
var parameter = Expression.Parameter(typeof(TEntity), "e");
|
||||||
Expression propertyValue = parameter;
|
Expression propertyValue = parameter;
|
||||||
Expression nullCheck = null;
|
Expression nullCheck = null;
|
||||||
|
|
||||||
foreach (var name in fullPropertyName.Split('.'))
|
foreach (var name in fullPropertyName.Split('.'))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
propertyValue = Expression.PropertyOrField(propertyValue, name);
|
propertyValue = Expression.PropertyOrField(propertyValue, name);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// name is not a direct property of field of propertyValue expression. construct a memberAccess then.
|
||||||
|
propertyValue = Expression.MakeMemberAccess(propertyValue, propertyInfo);
|
||||||
|
}
|
||||||
|
|
||||||
if (propertyValue.Type.IsNullable())
|
if (propertyValue.Type.IsNullable())
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
@ -340,7 +341,7 @@ namespace Sieve.Services
|
|||||||
|
|
||||||
if (property != null)
|
if (property != null)
|
||||||
{
|
{
|
||||||
result = result.OrderByDynamic(fullName, sortTerm.Descending, useThenBy);
|
result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -461,16 +462,35 @@ namespace Sieve.Services
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var incompatibleCustomMethod = parent?.GetType()
|
var incompatibleCustomMethods = parent?
|
||||||
.GetMethod(name,
|
.GetType()
|
||||||
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
.GetMethods
|
||||||
|
(
|
||||||
|
_options.Value.CaseSensitive
|
||||||
|
? BindingFlags.Default
|
||||||
|
: BindingFlags.IgnoreCase | BindingFlags.Public |
|
||||||
|
BindingFlags.Instance
|
||||||
|
)
|
||||||
|
.Where(method => string.Equals(method.Name, name,
|
||||||
|
_options.Value.CaseSensitive
|
||||||
|
? StringComparison.InvariantCulture
|
||||||
|
: StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
.ToList()
|
||||||
|
??
|
||||||
|
new List<MethodInfo>();
|
||||||
|
|
||||||
if (incompatibleCustomMethod != null)
|
if (incompatibleCustomMethods.Any())
|
||||||
{
|
{
|
||||||
var expected = typeof(IQueryable<TEntity>);
|
var incompatibles =
|
||||||
var actual = incompatibleCustomMethod.ReturnType;
|
from incompatibleCustomMethod in incompatibleCustomMethods
|
||||||
throw new SieveIncompatibleMethodException(name, expected, actual,
|
let expected = typeof(IQueryable<TEntity>)
|
||||||
|
let actual = incompatibleCustomMethod.ReturnType
|
||||||
|
select new SieveIncompatibleMethodException(name, expected, actual,
|
||||||
$"{name} failed. Expected a custom method for type {expected} but only found for type {actual}");
|
$"{name} failed. Expected a custom method for type {expected} but only found for type {actual}");
|
||||||
|
|
||||||
|
var aggregate = new AggregateException(incompatibles);
|
||||||
|
|
||||||
|
throw new SieveIncompatibleMethodException(aggregate.Message, aggregate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
10
SieveUnitTests/Abstractions/Entity/IBaseEntity.cs
Normal file
10
SieveUnitTests/Abstractions/Entity/IBaseEntity.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SieveUnitTests.Abstractions.Entity
|
||||||
|
{
|
||||||
|
public interface IBaseEntity
|
||||||
|
{
|
||||||
|
int Id { get; set; }
|
||||||
|
DateTimeOffset DateCreated { get; set; }
|
||||||
|
}
|
||||||
|
}
|
7
SieveUnitTests/Abstractions/Entity/IComment.cs
Normal file
7
SieveUnitTests/Abstractions/Entity/IComment.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace SieveUnitTests.Abstractions.Entity
|
||||||
|
{
|
||||||
|
public interface IComment: IBaseEntity
|
||||||
|
{
|
||||||
|
string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
24
SieveUnitTests/Abstractions/Entity/IPost.cs
Normal file
24
SieveUnitTests/Abstractions/Entity/IPost.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
|
namespace SieveUnitTests.Abstractions.Entity
|
||||||
|
{
|
||||||
|
public interface IPost: IBaseEntity
|
||||||
|
{
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
string Title { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
int LikeCount { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
int CommentCount { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
int? CategoryId { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
bool IsDraft { get; set; }
|
||||||
|
string ThisHasNoAttribute { get; set; }
|
||||||
|
string ThisHasNoAttributeButIsAccessible { get; set; }
|
||||||
|
int OnlySortableViaFluentApi { get; set; }
|
||||||
|
Comment TopComment { get; set; }
|
||||||
|
Comment FeaturedComment { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
|
||||||
namespace SieveUnitTests.Entities
|
namespace SieveUnitTests.Entities
|
||||||
{
|
{
|
||||||
public class BaseEntity
|
public class BaseEntity : IBaseEntity
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
|
||||||
namespace SieveUnitTests.Entities
|
namespace SieveUnitTests.Entities
|
||||||
{
|
{
|
||||||
public class Comment : BaseEntity
|
public class Comment : BaseEntity, IComment
|
||||||
{
|
{
|
||||||
[Sieve(CanFilter = true)]
|
[Sieve(CanFilter = true)]
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
|
||||||
namespace SieveUnitTests.Entities
|
namespace SieveUnitTests.Entities
|
||||||
{
|
{
|
||||||
public class Post : BaseEntity
|
public class Post : BaseEntity, IPost
|
||||||
{
|
{
|
||||||
|
|
||||||
[Sieve(CanFilter = true, CanSort = true)]
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||||||
using Sieve.Exceptions;
|
using Sieve.Exceptions;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
using SieveUnitTests.Services;
|
using SieveUnitTests.Services;
|
||||||
|
|
||||||
@ -564,5 +565,23 @@ namespace SieveUnitTests
|
|||||||
var filteredPosts = result.ToList();
|
var filteredPosts = result.ToList();
|
||||||
Assert.AreEqual(filteredPosts[0].Id, 2);
|
Assert.AreEqual(filteredPosts[0].Id, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "-CreateDate"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.AreEqual(posts[0].Id, 3);
|
||||||
|
Assert.AreEqual(posts[1].Id, 2);
|
||||||
|
Assert.AreEqual(posts[2].Id, 1);
|
||||||
|
Assert.AreEqual(posts[3].Id, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
588
SieveUnitTests/GeneralWithInterfaces.cs
Normal file
588
SieveUnitTests/GeneralWithInterfaces.cs
Normal file
@ -0,0 +1,588 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Sieve.Exceptions;
|
||||||
|
using Sieve.Models;
|
||||||
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
using SieveUnitTests.Entities;
|
||||||
|
using SieveUnitTests.Services;
|
||||||
|
|
||||||
|
namespace SieveUnitTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class GeneralWithInterfaces
|
||||||
|
{
|
||||||
|
private readonly SieveProcessor _processor;
|
||||||
|
private readonly IQueryable<IPost> _posts;
|
||||||
|
private readonly IQueryable<Comment> _comments;
|
||||||
|
|
||||||
|
public GeneralWithInterfaces()
|
||||||
|
{
|
||||||
|
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||||
|
new SieveCustomSortMethods(),
|
||||||
|
new SieveCustomFilterMethods());
|
||||||
|
|
||||||
|
_posts = new List<IPost>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 0,
|
||||||
|
Title = "A",
|
||||||
|
LikeCount = 100,
|
||||||
|
IsDraft = true,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = new Comment { Id = 0, Text = "A1" },
|
||||||
|
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = "B",
|
||||||
|
LikeCount = 50,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = 1,
|
||||||
|
TopComment = new Comment { Id = 3, Text = "B1" },
|
||||||
|
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 2,
|
||||||
|
Title = "C",
|
||||||
|
LikeCount = 0,
|
||||||
|
CategoryId = 1,
|
||||||
|
TopComment = new Comment { Id = 2, Text = "C1" },
|
||||||
|
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 3,
|
||||||
|
Title = "D",
|
||||||
|
LikeCount = 3,
|
||||||
|
IsDraft = true,
|
||||||
|
CategoryId = 2,
|
||||||
|
TopComment = new Comment { Id = 1, Text = "D1" },
|
||||||
|
FeaturedComment = new Comment { Id = 7, Text = "D2" }
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
_comments = new List<Comment>
|
||||||
|
{
|
||||||
|
new Comment() {
|
||||||
|
Id = 0,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
||||||
|
Text = "This is an old comment."
|
||||||
|
},
|
||||||
|
new Comment() {
|
||||||
|
Id = 1,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
||||||
|
Text = "This is a fairly new comment."
|
||||||
|
},
|
||||||
|
new Comment() {
|
||||||
|
Id = 2,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Text = "This is a brand new comment. (Text in braces)"
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ContainsCanBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title@=*a"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 0);
|
||||||
|
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]
|
||||||
|
public void ContainsIsCaseSensitive()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title@=a",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NotContainsWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title!@=D",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanFilterBools()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "IsDraft==false"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanSortBools()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "-IsDraft"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanFilterNullableInts()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "CategoryId==1"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void EqualsDoesntFailWithNonStringTypes()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "LikeCount==50",
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(model.GetFiltersParsed()[0].Values);
|
||||||
|
Console.WriteLine(model.GetFiltersParsed()[0].Operator);
|
||||||
|
Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed);
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 1);
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||||
|
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]
|
||||||
|
public void CustomFiltersWithOperatorsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "HasInTitle==A",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 0));
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersMixedWithUsualWork1()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Isnew,CategoryId==2",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersMixedWithUsualWork2()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "CategoryId==2,Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersOnDifferentSourcesCanShareName()
|
||||||
|
{
|
||||||
|
var postModel = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "CategoryId==2,Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var postResult = _processor.Apply(postModel, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(postResult.Any(p => p.Id == 3));
|
||||||
|
Assert.AreEqual(1, postResult.Count());
|
||||||
|
|
||||||
|
var commentModel = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var commentResult = _processor.Apply(commentModel, _comments);
|
||||||
|
|
||||||
|
Assert.IsTrue(commentResult.Any(c => c.Id == 2));
|
||||||
|
Assert.AreEqual(2, commentResult.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomSortsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "Popularity",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
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]
|
||||||
|
public void MethodNotFoundExceptionWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "does not exist",
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IncompatibleMethodExceptionsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "TestComment",
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.ThrowsException<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OrNameFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "(Title|LikeCount)==3",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
var entry = result.FirstOrDefault();
|
||||||
|
var resultCount = result.Count();
|
||||||
|
|
||||||
|
Assert.IsNotNull(entry);
|
||||||
|
Assert.AreEqual(1, resultCount);
|
||||||
|
Assert.AreEqual(3, entry.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||||
|
[DataRow("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||||
|
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
var entry = result.FirstOrDefault();
|
||||||
|
var resultCount = result.Count();
|
||||||
|
|
||||||
|
Assert.IsNotNull(entry);
|
||||||
|
Assert.AreEqual(1, resultCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CombinedAndOrWithSpaceFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title==D, (Title|LikeCount)==3",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
var entry = result.FirstOrDefault();
|
||||||
|
var resultCount = result.Count();
|
||||||
|
|
||||||
|
Assert.IsNotNull(entry);
|
||||||
|
Assert.AreEqual(1, resultCount);
|
||||||
|
Assert.AreEqual(3, entry.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OrValueFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title==C|D",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(2, result.Count());
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 2));
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OrValueFilteringWorks2()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Text@=(|)",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
Assert.AreEqual(1, result.Count());
|
||||||
|
Assert.AreEqual(2, result.FirstOrDefault().Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NestedFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "TopComment.Text!@=A",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(3, result.Count());
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.IsTrue(posts[0].TopComment.Text.Contains("B"));
|
||||||
|
Assert.IsTrue(posts[1].TopComment.Text.Contains("C"));
|
||||||
|
Assert.IsTrue(posts[2].TopComment.Text.Contains("D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NestedSortingWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "TopComment.Id",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.AreEqual(posts[0].Id, 0);
|
||||||
|
Assert.AreEqual(posts[1].Id, 3);
|
||||||
|
Assert.AreEqual(posts[2].Id, 2);
|
||||||
|
Assert.AreEqual(posts[3].Id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NestedFilteringWithIdenticTypesWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "(topc|featc)@=*2",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
|
||||||
|
model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "(topc|featc)@=*B",
|
||||||
|
};
|
||||||
|
|
||||||
|
result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(1, result.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void FilteringNullsWorks()
|
||||||
|
{
|
||||||
|
var posts = new List<Post>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "FeaturedComment.Text!@=Some value",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, posts);
|
||||||
|
Assert.AreEqual(0, result.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SortingNullsWorks()
|
||||||
|
{
|
||||||
|
var posts = new List<Post>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = new Comment { Id = 1 },
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 2,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "TopComment.Id",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, posts);
|
||||||
|
Assert.AreEqual(2, result.Count());
|
||||||
|
var sortedPosts = result.ToList();
|
||||||
|
Assert.AreEqual(sortedPosts[0].Id, 2);
|
||||||
|
Assert.AreEqual(sortedPosts[1].Id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void FilteringOnNullWorks()
|
||||||
|
{
|
||||||
|
var posts = new List<Post>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 2,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = new Comment { Id = 1, Text = null }
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "FeaturedComment.Text==null",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, posts);
|
||||||
|
Assert.AreEqual(1, result.Count());
|
||||||
|
var filteredPosts = result.ToList();
|
||||||
|
Assert.AreEqual(filteredPosts[0].Id, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "-CreateDate"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.AreEqual(posts[0].Id, 3);
|
||||||
|
Assert.AreEqual(posts[1].Id, 2);
|
||||||
|
Assert.AreEqual(posts[2].Id, 1);
|
||||||
|
Assert.AreEqual(posts[3].Id, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
namespace SieveUnitTests.Services
|
namespace SieveUnitTests.Services
|
||||||
@ -39,6 +40,39 @@ namespace SieveUnitTests.Services
|
|||||||
.CanFilter()
|
.CanFilter()
|
||||||
.HasName("featc");
|
.HasName("featc");
|
||||||
|
|
||||||
|
mapper
|
||||||
|
.Property<Post>(p => p.DateCreated)
|
||||||
|
.CanSort()
|
||||||
|
.HasName("CreateDate");
|
||||||
|
|
||||||
|
// interfaces
|
||||||
|
mapper.Property<IPost>(p => p.ThisHasNoAttributeButIsAccessible)
|
||||||
|
.CanSort()
|
||||||
|
.CanFilter()
|
||||||
|
.HasName("shortname");
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.TopComment.Text)
|
||||||
|
.CanFilter();
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.TopComment.Id)
|
||||||
|
.CanSort();
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.OnlySortableViaFluentApi)
|
||||||
|
.CanSort();
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.TopComment.Text)
|
||||||
|
.CanFilter()
|
||||||
|
.HasName("topc");
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.FeaturedComment.Text)
|
||||||
|
.CanFilter()
|
||||||
|
.HasName("featc");
|
||||||
|
|
||||||
|
mapper
|
||||||
|
.Property<IPost>(p => p.DateCreated)
|
||||||
|
.CanSort()
|
||||||
|
.HasName("CreateDate");
|
||||||
|
|
||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
namespace SieveUnitTests.Services
|
namespace SieveUnitTests.Services
|
||||||
@ -38,5 +39,31 @@ namespace SieveUnitTests.Services
|
|||||||
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
|
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IQueryable<IPost> IsNew(IQueryable<IPost> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
var result = source.Where(p => p.LikeCount < 100);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<IPost> HasInTitle(IQueryable<IPost> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
var result = source.Where(p => p.Title.Contains(values[0]));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<IComment> IsNew(IQueryable<IComment> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-2));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<IComment> TestComment(IQueryable<IComment> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
namespace SieveUnitTests.Services
|
namespace SieveUnitTests.Services
|
||||||
@ -17,7 +18,18 @@ namespace SieveUnitTests.Services
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IQueryable<T> Oldest<T>(IQueryable<T> source, bool useThenBy, bool desc) where T : BaseEntity
|
public IQueryable<IPost> Popularity(IQueryable<IPost> source, bool useThenBy, bool desc)
|
||||||
|
{
|
||||||
|
var result = useThenBy ?
|
||||||
|
((IOrderedQueryable<IPost>)source).ThenBy(p => p.LikeCount) :
|
||||||
|
source.OrderBy(p => p.LikeCount)
|
||||||
|
.ThenBy(p => p.CommentCount)
|
||||||
|
.ThenBy(p => p.DateCreated);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<T> Oldest<T>(IQueryable<T> source, bool useThenBy, bool desc) where T : IBaseEntity
|
||||||
{
|
{
|
||||||
var result = useThenBy ?
|
var result = useThenBy ?
|
||||||
((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
|
((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
|
|
||||||
namespace SieveUnitTests
|
namespace SieveUnitTests.Services
|
||||||
{
|
{
|
||||||
public class SieveOptionsAccessor : IOptions<SieveOptions>
|
public class SieveOptionsAccessor : IOptions<SieveOptions>
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user