collection handling.
and auto null checking if enabled.
This commit is contained in:
parent
babce13871
commit
e3660877ef
@ -99,6 +99,81 @@ namespace PoweredSoft.DynamicLinq.Test
|
||||
Assert.IsTrue(second.Id == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCreateFilterExpressionCheckNull()
|
||||
{
|
||||
var authors = new List<Author>()
|
||||
{
|
||||
new Author
|
||||
{
|
||||
Id = 1,
|
||||
FirstName = "David",
|
||||
LastName = "Lebee",
|
||||
Posts = new List<Post>
|
||||
{
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
AuthorId = 1,
|
||||
Title = "Match",
|
||||
Content = "ABC",
|
||||
Comments = new List<Comment>()
|
||||
{
|
||||
new Comment()
|
||||
{
|
||||
Id = 1,
|
||||
DisplayName = "John Doe",
|
||||
CommentText = "!@#$!@#!@#",
|
||||
Email = "john.doe@me.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
AuthorId = 1,
|
||||
Title = "Match",
|
||||
Content = "ABC"
|
||||
}
|
||||
}
|
||||
},
|
||||
new Author
|
||||
{
|
||||
Id = 2,
|
||||
FirstName = "Chuck",
|
||||
LastName = "Norris",
|
||||
Posts = new List<Post>
|
||||
{
|
||||
new Post
|
||||
{
|
||||
Id = 3,
|
||||
AuthorId = 2,
|
||||
Title = "Match",
|
||||
Content = "ASD"
|
||||
},
|
||||
new Post
|
||||
{
|
||||
Id = 4,
|
||||
AuthorId = 2,
|
||||
Title = "DontMatch",
|
||||
Content = "ASD"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// the query.
|
||||
var query = authors.AsQueryable();
|
||||
|
||||
query = query.Query(qb =>
|
||||
{
|
||||
qb.NullChecking();
|
||||
qb.And("Posts.Comments.Email", ConditionOperators.Equal, "john.doe@me.com", collectionHandling: QueryCollectionHandling.Any);
|
||||
});
|
||||
|
||||
Assert.AreEqual(1, query.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCreateFilterExpression()
|
||||
{
|
||||
@ -133,7 +208,8 @@ namespace PoweredSoft.DynamicLinq.Test
|
||||
Id = 2,
|
||||
AuthorId = 1,
|
||||
Title = "Match",
|
||||
Content = "ABC"
|
||||
Content = "ABC",
|
||||
Comments = new List<Comment>()
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -150,6 +226,7 @@ namespace PoweredSoft.DynamicLinq.Test
|
||||
AuthorId = 2,
|
||||
Title = "Match",
|
||||
Content = "ASD",
|
||||
Comments = new List<Comment>()
|
||||
},
|
||||
new Post
|
||||
{
|
||||
@ -157,6 +234,7 @@ namespace PoweredSoft.DynamicLinq.Test
|
||||
AuthorId = 2,
|
||||
Title = "DontMatch",
|
||||
Content = "ASD",
|
||||
Comments = new List<Comment>()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,10 +243,12 @@ namespace PoweredSoft.DynamicLinq.Test
|
||||
// the query.
|
||||
var query = authors.AsQueryable();
|
||||
|
||||
var allExpression = QueryableHelpers.CreateFilterExpression<Author>("Posts.Title", ConditionOperators.Equal, "Match", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionCondition.All);
|
||||
var anyExpression = QueryableHelpers.CreateFilterExpression<Author>("Posts.Title", ConditionOperators.Equal, "Match", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionCondition.Any);
|
||||
var allExpression = QueryableHelpers.CreateFilterExpression<Author>("Posts.Title", ConditionOperators.Equal, "Match", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling.All);
|
||||
var anyExpression = QueryableHelpers.CreateFilterExpression<Author>("Posts.Title", ConditionOperators.Equal, "Match", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling.Any);
|
||||
var anyExpression2 = QueryableHelpers.CreateFilterExpression<Author>("Posts.Comments.Email", ConditionOperators.Equal, "John.doe@me.com", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling.Any);
|
||||
Assert.AreEqual(1, query.Count(allExpression));
|
||||
Assert.AreEqual(2, query.Count(anyExpression));
|
||||
Assert.AreEqual(1, query.Count(anyExpression2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace PoweredSoft.DynamicLinq
|
||||
public enum ConditionOperators
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThan,
|
||||
@ -25,7 +26,7 @@ namespace PoweredSoft.DynamicLinq
|
||||
ConvertConstantToComparedPropertyOrField
|
||||
}
|
||||
|
||||
public enum QueryCollectionCondition
|
||||
public enum QueryCollectionHandling
|
||||
{
|
||||
Any,
|
||||
All
|
||||
|
@ -15,6 +15,8 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
|
||||
public Type QueryableType { get; set; }
|
||||
|
||||
public bool IsNullCheckingEnabled { get; protected set; } = false;
|
||||
|
||||
public List<QueryBuilderFilter> Filters { get; protected set; } = new List<QueryBuilderFilter>();
|
||||
|
||||
public List<QueryBuilderSort> Sorts { get; protected set; } = new List<QueryBuilderSort>();
|
||||
@ -24,9 +26,15 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
Query = query;
|
||||
}
|
||||
|
||||
public QueryBuilder<T> NullChecking(bool check = true)
|
||||
{
|
||||
IsNullCheckingEnabled = check;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Compare(string path, ConditionOperators conditionOperators, object value,
|
||||
QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField,
|
||||
bool and = true)
|
||||
bool and = true, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any)
|
||||
{
|
||||
Filters.Add(new QueryBuilderFilter
|
||||
{
|
||||
@ -34,7 +42,8 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
ConditionOperator = conditionOperators,
|
||||
Path = path,
|
||||
Value = value,
|
||||
ConvertStrategy = convertStrategy
|
||||
ConvertStrategy = convertStrategy,
|
||||
CollectionHandling = collectionHandling
|
||||
});
|
||||
|
||||
return this;
|
||||
@ -55,6 +64,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
{
|
||||
// create query builder for same type.
|
||||
var qb = new QueryBuilder<T>(Query);
|
||||
qb.NullChecking(IsNullCheckingEnabled);
|
||||
|
||||
// callback.
|
||||
subQuery(qb);
|
||||
@ -69,11 +79,13 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder<T> And(string path, ConditionOperators conditionOperator, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField)
|
||||
=> Compare(path, conditionOperator, value, convertStrategy: convertStrategy, and: true);
|
||||
public QueryBuilder<T> And(string path, ConditionOperators conditionOperator, object value,
|
||||
QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any)
|
||||
=> Compare(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, and: true);
|
||||
|
||||
public QueryBuilder<T> Or(string path, ConditionOperators conditionOperator, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField)
|
||||
=> Compare(path, conditionOperator, value, convertStrategy: convertStrategy, and: false);
|
||||
public QueryBuilder<T> Or(string path, ConditionOperators conditionOperator, object value,
|
||||
QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any)
|
||||
=> Compare(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, and: false);
|
||||
|
||||
public QueryBuilder<T> And(Action<QueryBuilder<T>> subQuery)
|
||||
=> SubQuery(subQuery, true);
|
||||
@ -189,9 +201,9 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
else
|
||||
{
|
||||
if (filter.And)
|
||||
temp = Expression.Lambda<Func<T, bool>>(Expression.And(temp.Body, innerExpression.Body), parameter);
|
||||
temp = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(temp.Body, innerExpression.Body), parameter);
|
||||
else
|
||||
temp = Expression.Lambda<Func<T, bool>>(Expression.Or(temp.Body, innerExpression.Body), parameter);
|
||||
temp = Expression.Lambda<Func<T, bool>>(Expression.OrElse(temp.Body, innerExpression.Body), parameter);
|
||||
}
|
||||
|
||||
});
|
||||
@ -207,7 +219,8 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
filter.Value,
|
||||
filter.ConvertStrategy,
|
||||
filter.CollectionHandling,
|
||||
parameter: parameter
|
||||
parameter: parameter,
|
||||
nullChecking: IsNullCheckingEnabled
|
||||
);
|
||||
|
||||
return ret;
|
||||
|
@ -14,6 +14,6 @@ namespace PoweredSoft.DynamicLinq.Fluent
|
||||
public bool And { get; set; }
|
||||
public QueryConvertStrategy ConvertStrategy { get; set; }
|
||||
public List<QueryBuilderFilter> Filters { get; set; } = new List<QueryBuilderFilter>();
|
||||
public QueryCollectionCondition CollectionHandling { get; set; }
|
||||
public QueryCollectionHandling CollectionHandling { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ namespace PoweredSoft.DynamicLinq.Helpers
|
||||
|
||||
if (conditionOperator == ConditionOperators.Equal)
|
||||
ret = Expression.Equal(member, constant);
|
||||
else if (conditionOperator == ConditionOperators.NotEqual)
|
||||
ret = Expression.NotEqual(member, constant);
|
||||
else if (conditionOperator == ConditionOperators.GreaterThan)
|
||||
ret = Expression.GreaterThan(member, constant);
|
||||
else if (conditionOperator == ConditionOperators.GreaterThanOrEqual)
|
||||
@ -119,8 +121,8 @@ namespace PoweredSoft.DynamicLinq.Helpers
|
||||
return query;
|
||||
}
|
||||
|
||||
internal static Expression InternalCreateFilterExpression(int recursionStep, Type type, ParameterExpression parameter, Expression current, List<string> parts,
|
||||
ConditionOperators condition, object value, QueryConvertStrategy convertStrategy, QueryCollectionCondition collectionHandling)
|
||||
internal static Expression InternalCreateFilterExpression(int recursionStep, Type type, ParameterExpression parameter, Expression current, List<string> parts,
|
||||
ConditionOperators condition, object value, QueryConvertStrategy convertStrategy, QueryCollectionHandling collectionHandling, bool nullChecking)
|
||||
{
|
||||
var partStr = parts.First();
|
||||
var isLast = parts.Count == 1;
|
||||
@ -130,8 +132,9 @@ namespace PoweredSoft.DynamicLinq.Helpers
|
||||
|
||||
// TODO : maybe support that last part is collection but what do we do?
|
||||
// not supported yet.
|
||||
if (isLast && IsEnumerable(memberExpression))
|
||||
throw new NotSupportedException("Last part must not be a collection");
|
||||
if (isLast && IsEnumerable(memberExpression) && value != null)
|
||||
throw new NotSupportedException("Can only compare collection to null");
|
||||
|
||||
|
||||
// create the expression and return it.
|
||||
if (isLast)
|
||||
@ -141,30 +144,49 @@ namespace PoweredSoft.DynamicLinq.Helpers
|
||||
var lambda = Expression.Lambda(filterExpression, parameter);
|
||||
return lambda;
|
||||
}
|
||||
|
||||
|
||||
// null check.
|
||||
Expression nullCheckExpression = null;
|
||||
if (nullChecking)
|
||||
nullCheckExpression = Expression.NotEqual(memberExpression, Expression.Constant(null));
|
||||
|
||||
if (IsEnumerable(memberExpression))
|
||||
{
|
||||
var listGenericArgumentType = memberExpression.Type.GetGenericArguments().First();
|
||||
var innerParameter = Expression.Parameter(listGenericArgumentType, $"t{++recursionStep}");
|
||||
var innerLambda = InternalCreateFilterExpression(recursionStep, listGenericArgumentType, innerParameter, innerParameter, parts.Skip(1).ToList(), condition, value, convertStrategy, collectionHandling);
|
||||
var innerLambda = InternalCreateFilterExpression(recursionStep, listGenericArgumentType, innerParameter, innerParameter, parts.Skip(1).ToList(), condition, value, convertStrategy, collectionHandling, nullChecking);
|
||||
|
||||
// the collection method.
|
||||
var collectionMethod = GetCollectionMethod(collectionHandling);
|
||||
var genericMethod = collectionMethod.MakeGenericMethod(listGenericArgumentType);
|
||||
var callResult = Expression.Call(genericMethod, memberExpression, innerLambda);
|
||||
var expressionResult = Expression.Lambda(callResult, parameter);
|
||||
return expressionResult;
|
||||
}
|
||||
|
||||
// standard property or field.
|
||||
return InternalCreateFilterExpression(recursionStep, type, parameter, memberExpression, parts.Skip(1).ToList(), condition, value, convertStrategy, collectionHandling);
|
||||
if (nullCheckExpression != null)
|
||||
{
|
||||
var nullCheckResult = Expression.AndAlso(nullCheckExpression, callResult);
|
||||
return Expression.Lambda(nullCheckResult, parameter);
|
||||
}
|
||||
|
||||
return Expression.Lambda(callResult, parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nullCheckExpression != null)
|
||||
{
|
||||
var pathExpr = InternalCreateFilterExpression(recursionStep, type, parameter, memberExpression, parts.Skip(1).ToList(), condition, value, convertStrategy, collectionHandling, nullChecking);
|
||||
var nullCheckResult = Expression.AndAlso(nullCheckExpression, pathExpr);
|
||||
return nullCheckResult;
|
||||
}
|
||||
|
||||
return InternalCreateFilterExpression(recursionStep, type, parameter, memberExpression, parts.Skip(1).ToList(), condition, value, convertStrategy, collectionHandling, nullChecking);
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodInfo GetCollectionMethod(QueryCollectionCondition collectionHandling)
|
||||
public static MethodInfo GetCollectionMethod(QueryCollectionHandling collectionHandling)
|
||||
{
|
||||
if (collectionHandling == QueryCollectionCondition.All)
|
||||
if (collectionHandling == QueryCollectionHandling.All)
|
||||
return Constants.AllMethod;
|
||||
else if (collectionHandling == QueryCollectionCondition.Any)
|
||||
else if (collectionHandling == QueryCollectionHandling.Any)
|
||||
return Constants.AnyMethod;
|
||||
|
||||
throw new NotSupportedException($"{collectionHandling} is not supported");
|
||||
@ -174,14 +196,15 @@ namespace PoweredSoft.DynamicLinq.Helpers
|
||||
ConditionOperators condition,
|
||||
object value,
|
||||
QueryConvertStrategy convertStrategy,
|
||||
QueryCollectionCondition collectionHandling = QueryCollectionCondition.Any,
|
||||
ParameterExpression parameter = null)
|
||||
QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any,
|
||||
ParameterExpression parameter = null,
|
||||
bool nullChecking = false)
|
||||
{
|
||||
if (parameter == null)
|
||||
parameter = Expression.Parameter(typeof(T), "t");
|
||||
|
||||
var parts = path.Split('.').ToList();
|
||||
var result = InternalCreateFilterExpression(1, typeof(T), parameter, parameter, parts, condition, value, convertStrategy, collectionHandling);
|
||||
var result = InternalCreateFilterExpression(1, typeof(T), parameter, parameter, parts, condition, value, convertStrategy, collectionHandling, nullChecking);
|
||||
var ret = result as Expression<Func<T, bool>>;
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user