diff --git a/PoweredSoft.DynamicLinq.Test/InTests.cs b/PoweredSoft.DynamicLinq.Test/InTests.cs new file mode 100644 index 0000000..7beaece --- /dev/null +++ b/PoweredSoft.DynamicLinq.Test/InTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using PoweredSoft.DynamicLinq; +using PoweredSoft.DynamicLinq.Test.Helpers; + +namespace PoweredSoft.DynamicLinq.Test +{ + [TestClass] + public class InTests + { + internal List Persons = new List + { + new MockPersonObject { FirstName = "David", LastName = "Lebee", Age = 28 }, + new MockPersonObject { FirstName = "Michaela", LastName = "Vickar", Age = 27 }, + new MockPersonObject { FirstName = "John", LastName = "Doe", Age = 28 }, + new MockPersonObject { FirstName = "Chuck", LastName = "Norris", Age = 50 }, + new MockPersonObject { FirstName = "Michael", LastName = "Jackson", Age = 58 } + }; + + [TestMethod] + public void In() + { + IQueryable a, b; + var ageGroup = new List() { 28, 27, 50 }; + + a = Persons.AsQueryable().Query(t => t.In("Age", ageGroup)); + b = Persons.AsQueryable().Where(t => ageGroup.Contains(t.Age)); + QueryableAssert.AreEqual(a, b); + } + + [TestMethod] + public void NotIn() + { + IQueryable a, b; + var ageGroup = new List() { 50, 58 }; + a = Persons.AsQueryable().Query(t => t.NotIn("Age", ageGroup)); + b = Persons.AsQueryable().Where(t => !ageGroup.Contains(t.Age)); + QueryableAssert.AreEqual(a, b); + } + + [TestMethod] + public void InString() + { + IQueryable a, b; + var group = new List() { "David", "Michaela" }; + a = Persons.AsQueryable().Query(t => t.In("FirstName", group)); + b = Persons.AsQueryable().Where(t => group.Contains(t.FirstName)); + QueryableAssert.AreEqual(a, b); + } + + [TestMethod] + public void DiffTypeListConversion() + { + IQueryable a, b; + var ageGroup = new List() { "28", "27", "50" }; + var ageGroupInt = ageGroup.Select(t => Convert.ToInt32(t)).ToList(); + + a = Persons.AsQueryable().Query(t => t.In("Age", ageGroup)); + b = Persons.AsQueryable().Where(t => ageGroupInt.Contains(t.Age)); + QueryableAssert.AreEqual(a, b); + } + } +} diff --git a/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj b/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj index 98ba927..1c73b67 100644 --- a/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj +++ b/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj @@ -63,6 +63,7 @@ + diff --git a/PoweredSoft.DynamicLinq/Constants.cs b/PoweredSoft.DynamicLinq/Constants.cs index 972ca15..f4f8eb6 100644 --- a/PoweredSoft.DynamicLinq/Constants.cs +++ b/PoweredSoft.DynamicLinq/Constants.cs @@ -17,7 +17,9 @@ namespace PoweredSoft.DynamicLinq LessThanOrEqual, Contains, StartsWith, - EndsWith + EndsWith, + In, + NotIn } public enum QueryConvertStrategy diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.shortcuts.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.shortcuts.cs index 10cb044..5951901 100644 --- a/PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.shortcuts.cs +++ b/PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.shortcuts.cs @@ -118,5 +118,27 @@ namespace PoweredSoft.DynamicLinq.Fluent public QueryBuilderBase OrEndsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) => Or(path, ConditionOperators.EndsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); #endregion + + #region In + public QueryBuilderBase In(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) + => And(path, ConditionOperators.In, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); + + public QueryBuilderBase AndIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) + => And(path, ConditionOperators.In, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); + + public QueryBuilderBase OrIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) + => Or(path, ConditionOperators.In, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); + #endregion + + #region NotIn + public QueryBuilderBase NotIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) + => And(path, ConditionOperators.NotIn, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); + + public QueryBuilderBase AndNotIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) + => And(path, ConditionOperators.NotIn, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); + + public QueryBuilderBase OrNotIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) + => Or(path, ConditionOperators.NotIn, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); + #endregion } } diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs index 2704a9b..455766e 100644 --- a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -151,6 +152,17 @@ namespace PoweredSoft.DynamicLinq.Helpers return query; } + /* + * var methodInfo = typeof(List).GetMethod("Contains", new Type[] { typeof(Guid) }); + var list = Expression.Constant(ids); + var param = Expression.Parameter(typeof(T), "t"); + var value = Expression.PropertyOrField(param, idField); + var body = Expression.Call(list, methodInfo, value); + var lambda = Expression.Lambda>(body, param); + query = query.Where(lambda); + + */ + internal static Expression InternalCreateFilterExpression(int recursionStep, Type type, ParameterExpression parameter, Expression current, List parts, ConditionOperators condition, object value, QueryConvertStrategy convertStrategy, QueryCollectionHandling collectionHandling, bool nullChecking, StringComparison? stringComparison) { @@ -169,10 +181,17 @@ namespace PoweredSoft.DynamicLinq.Helpers // create the expression and return it. if (isLast) { - var constant = QueryableHelpers.ResolveConstant(memberExpression, value, convertStrategy); - var filterExpression = QueryableHelpers.GetConditionExpressionForMember(parameter, memberExpression, condition, constant, stringComparison); - var lambda = Expression.Lambda(filterExpression, parameter); - return lambda; + if (condition == ConditionOperators.In || condition == ConditionOperators.NotIn) + { + return InAndNotIn(parameter, condition, value, convertStrategy, memberExpression); + } + else + { + var constant = QueryableHelpers.ResolveConstant(memberExpression, value, convertStrategy); + var filterExpression = QueryableHelpers.GetConditionExpressionForMember(parameter, memberExpression, condition, constant, stringComparison); + var lambda = Expression.Lambda(filterExpression, parameter); + return lambda; + } } // null check. @@ -212,6 +231,42 @@ namespace PoweredSoft.DynamicLinq.Helpers } } + public static Expression InAndNotIn(ParameterExpression parameter, ConditionOperators condition, object value, QueryConvertStrategy convertStrategy, MemberExpression memberExpression) + { + var enumerableValue = value as IEnumerable; + if (enumerableValue == null) + throw new Exception($"to use {ConditionOperators.In} your value must at least be IEnumerable"); + + var enumerableType = GetEnumerableType(enumerableValue); + var finalType = convertStrategy == QueryConvertStrategy.ConvertConstantToComparedPropertyOrField ? memberExpression.Type : enumerableType; + var genericListOfEnumerableType = typeof(List<>).MakeGenericType(memberExpression.Type); + var containsMethod = genericListOfEnumerableType.GetMethod("Contains", new Type[] { finalType }); + var list = Activator.CreateInstance(genericListOfEnumerableType) as IList; + foreach (var o in enumerableValue) + { + if (convertStrategy == QueryConvertStrategy.ConvertConstantToComparedPropertyOrField) + list.Add(TypeHelpers.ConvertFrom(memberExpression.Type, o)); + else + list.Add(o); + } + + var body = Expression.Call(Expression.Constant(list), containsMethod, memberExpression) as Expression; + + if (condition == ConditionOperators.NotIn) + body = Expression.Not(body); + + var lambda = Expression.Lambda(body, parameter); + return lambda; + } + + private static Type GetEnumerableType(IEnumerable enumerableValue) + { + foreach (var o in enumerableValue) + return o.GetType(); + + return null; + } + public static MethodInfo GetCollectionMethod(QueryCollectionHandling collectionHandling) { if (collectionHandling == QueryCollectionHandling.All)