mirror of
				https://github.com/Biarity/Sieve.git
				synced 2025-11-04 02:39:00 +01:00 
			
		
		
		
	Merge pull request #98 from hasanmanzak/master
OrderByDynamic is modified to be able to handle inherited members...
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
 | 
			
		||||
namespace Sieve.Extensions
 | 
			
		||||
{
 | 
			
		||||
@@ -9,10 +10,11 @@ namespace Sieve.Extensions
 | 
			
		||||
        public static IQueryable<TEntity> OrderByDynamic<TEntity>(
 | 
			
		||||
            this IQueryable<TEntity> source,
 | 
			
		||||
            string fullPropertyName,
 | 
			
		||||
            PropertyInfo propertyInfo,
 | 
			
		||||
            bool desc,
 | 
			
		||||
            bool useThenBy)
 | 
			
		||||
        {
 | 
			
		||||
            var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName);
 | 
			
		||||
            var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName, propertyInfo);
 | 
			
		||||
 | 
			
		||||
            var command = desc
 | 
			
		||||
                ? (useThenBy ? "ThenByDescending" : "OrderByDescending")
 | 
			
		||||
@@ -28,7 +30,11 @@ namespace Sieve.Extensions
 | 
			
		||||
            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");
 | 
			
		||||
            Expression propertyValue = parameter;
 | 
			
		||||
@@ -36,7 +42,15 @@ namespace Sieve.Extensions
 | 
			
		||||
 | 
			
		||||
            foreach (var name in fullPropertyName.Split('.'))
 | 
			
		||||
            {
 | 
			
		||||
                propertyValue = Expression.PropertyOrField(propertyValue, name);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    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())
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
@@ -340,7 +341,7 @@ namespace Sieve.Services
 | 
			
		||||
 | 
			
		||||
                if (property != null)
 | 
			
		||||
                {
 | 
			
		||||
                    result = result.OrderByDynamic(fullName, sortTerm.Descending, useThenBy);
 | 
			
		||||
                    result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
@@ -461,16 +462,35 @@ namespace Sieve.Services
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var incompatibleCustomMethod = parent?.GetType()
 | 
			
		||||
                    .GetMethod(name,
 | 
			
		||||
                    _options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
 | 
			
		||||
                var incompatibleCustomMethods = parent?
 | 
			
		||||
                                                    .GetType()
 | 
			
		||||
                                                    .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 actual = incompatibleCustomMethod.ReturnType;
 | 
			
		||||
                    throw new SieveIncompatibleMethodException(name, expected, actual,
 | 
			
		||||
                        $"{name} failed. Expected a custom method for type {expected} but only found for type {actual}");
 | 
			
		||||
                    var incompatibles =
 | 
			
		||||
                        from incompatibleCustomMethod in incompatibleCustomMethods
 | 
			
		||||
                        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}");
 | 
			
		||||
 | 
			
		||||
                    var aggregate = new AggregateException(incompatibles);
 | 
			
		||||
 | 
			
		||||
                    throw new SieveIncompatibleMethodException(aggregate.Message, aggregate);
 | 
			
		||||
                }
 | 
			
		||||
                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 Sieve.Attributes;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests.Entities
 | 
			
		||||
{
 | 
			
		||||
    public class BaseEntity
 | 
			
		||||
    public class BaseEntity : IBaseEntity
 | 
			
		||||
    {
 | 
			
		||||
        public int Id { get; set; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
using Sieve.Attributes;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests.Entities
 | 
			
		||||
{
 | 
			
		||||
    public class Comment : BaseEntity
 | 
			
		||||
    public class Comment : BaseEntity, IComment
 | 
			
		||||
    {
 | 
			
		||||
        [Sieve(CanFilter = true)]
 | 
			
		||||
        public string Text { get; set; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Sieve.Attributes;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests.Entities
 | 
			
		||||
{
 | 
			
		||||
    public class Post : BaseEntity
 | 
			
		||||
    public class Post : BaseEntity, IPost
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        [Sieve(CanFilter = true, CanSort = true)]
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
			
		||||
using Sieve.Exceptions;
 | 
			
		||||
using Sieve.Models;
 | 
			
		||||
using Sieve.Services;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
using SieveUnitTests.Entities;
 | 
			
		||||
using SieveUnitTests.Services;
 | 
			
		||||
 | 
			
		||||
@@ -564,5 +565,23 @@ namespace SieveUnitTests
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 Sieve.Models;
 | 
			
		||||
using Sieve.Services;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
using SieveUnitTests.Entities;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests.Services
 | 
			
		||||
@@ -39,6 +40,39 @@ namespace SieveUnitTests.Services
 | 
			
		||||
                .CanFilter()
 | 
			
		||||
                .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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Sieve.Services;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
using SieveUnitTests.Entities;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests.Services
 | 
			
		||||
@@ -38,5 +39,31 @@ namespace SieveUnitTests.Services
 | 
			
		||||
            var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
 | 
			
		||||
            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 Sieve.Services;
 | 
			
		||||
using SieveUnitTests.Abstractions.Entity;
 | 
			
		||||
using SieveUnitTests.Entities;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests.Services
 | 
			
		||||
@@ -17,7 +18,18 @@ namespace SieveUnitTests.Services
 | 
			
		||||
            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 ?
 | 
			
		||||
                ((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
using Microsoft.Extensions.Options;
 | 
			
		||||
using Sieve.Models;
 | 
			
		||||
 | 
			
		||||
namespace SieveUnitTests
 | 
			
		||||
namespace SieveUnitTests.Services
 | 
			
		||||
{
 | 
			
		||||
    public class SieveOptionsAccessor : IOptions<SieveOptions>
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user