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