Query builder now supports collections.
Lots of refactoring for this support.
This commit is contained in:
		
							parent
							
								
									507b64c34b
								
							
						
					
					
						commit
						6fa59c3035
					
				| @ -99,130 +99,76 @@ namespace PoweredSoft.DynamicLinq.Test | ||||
|             Assert.IsTrue(second.Id == 1); | ||||
|         } | ||||
| 
 | ||||
|         //[TestMethod] | ||||
|         //public void Test() | ||||
|         //{ | ||||
|         //    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", | ||||
|         //                } | ||||
|         //            } | ||||
|         //        } | ||||
|         //    }; | ||||
|         [TestMethod] | ||||
|         public void TestCreateFilterExpression() | ||||
|         { | ||||
|             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(); | ||||
|             // the query. | ||||
|             var query = authors.AsQueryable(); | ||||
| 
 | ||||
|         //    //// first recursion. | ||||
|         //    //var typeOfClass = typeof(Author); | ||||
|         //    //var parameter = Expression.Parameter(typeOfClass, "t"); | ||||
|         //    //var posts = Expression.PropertyOrField(parameter, "Posts"); | ||||
| 
 | ||||
|         //    //// second recursion | ||||
|         //    //{ | ||||
|         //    //    var subListType = posts.Type.GetGenericArguments().First(); | ||||
| 
 | ||||
|         //    //    var innerParam = Expression.Parameter(subListType, "t2"); | ||||
|         //    //    var field = Expression.PropertyOrField(innerParam, "Title"); | ||||
|         //    //    var innerCondition = PoweredSoft.DynamicLinq.Helpers.QueryableHelpers.GetConditionExpressionForMember(innerParam, field, ConditionOperators.Equal, Expression.Constant("Match")); | ||||
|         //    //    var lambda = Expression.Lambda(innerCondition, innerParam); | ||||
| 
 | ||||
|         //    //    // any | ||||
|         //    //    var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "All" && t.GetParameters().Count() == 2); | ||||
|         //    //    var genericAnyMethod = anyMethod.MakeGenericMethod(subListType); | ||||
|         //    //    var subExpression = Expression.Call(genericAnyMethod, posts, lambda); | ||||
| 
 | ||||
|         //    //    var finalLambda = Expression.Lambda<Func<Author, bool>>(subExpression, parameter); | ||||
|         //    //    query = query.Where(finalLambda); | ||||
|         //    //} | ||||
| 
 | ||||
|         //    var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "Any" && t.GetParameters().Count() == 2); | ||||
| 
 | ||||
|         //    // first recursion. | ||||
|         //    var typeOfClass = typeof(Author); | ||||
|         //    var parameter = Expression.Parameter(typeOfClass, "t"); | ||||
|         //    var posts = Expression.PropertyOrField(parameter, "Posts"); | ||||
| 
 | ||||
|         //    // second recursion | ||||
|         //    { | ||||
|         //        var subListType = posts.Type.GetGenericArguments().First(); | ||||
| 
 | ||||
|         //        var innerParam = Expression.Parameter(subListType, "t2"); | ||||
|         //        var field = Expression.PropertyOrField(innerParam, "Comments"); | ||||
| 
 | ||||
|         //        // any | ||||
|         //        var genericAnyMethod = anyMethod.MakeGenericMethod(subListType); | ||||
| 
 | ||||
|         //        // third recursion | ||||
|         //        { | ||||
|         //            var innerParam2 = Expression.Parameter(typeof(Comment), "t3"); | ||||
|         //            var field2 = Expression.PropertyOrField(innerParam2, "Id"); | ||||
|         //            var innerCondition2 = QueryableHelpers.GetConditionExpressionForMember(innerParam2, field2, ConditionOperators.Equal, Expression.Constant(1L)); | ||||
|         //            var lambda = Expression.Lambda(innerCondition2, innerParam2); | ||||
| 
 | ||||
|         //            var generateAnyMethod2 = anyMethod.MakeGenericMethod(typeof(Comment)); | ||||
|         //            var subExpression2 = Expression.Call(generateAnyMethod2, field, lambda); | ||||
| 
 | ||||
|         //            var previousLambda = Expression.Lambda<Func<Post, bool>>(subExpression2, innerParam); | ||||
| 
 | ||||
|         //            var subExpression = Expression.Call(genericAnyMethod, posts, previousLambda); | ||||
| 
 | ||||
|         //            var finalLambda = Expression.Lambda<Func<Author, bool>>(subExpression, parameter); | ||||
|         //            query = query.Where(finalLambda); | ||||
|         //        } | ||||
|         //    } | ||||
|         //} | ||||
|             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); | ||||
|             Assert.AreEqual(1, query.Count(allExpression)); | ||||
|             Assert.AreEqual(2, query.Count(anyExpression)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -25,10 +25,18 @@ namespace PoweredSoft.DynamicLinq | ||||
|         ConvertConstantToComparedPropertyOrField | ||||
|     } | ||||
| 
 | ||||
|     public enum QueryCollectionCondition | ||||
|     { | ||||
|         Any, | ||||
|         All | ||||
|     } | ||||
| 
 | ||||
|     internal static class Constants | ||||
|     { | ||||
|         internal static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains"); | ||||
|         internal static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }); | ||||
|         internal static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }); | ||||
|         internal static readonly MethodInfo AnyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "Any" && t.GetParameters().Count() == 2); | ||||
|         internal static readonly MethodInfo AllMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "All" && t.GetParameters().Count() == 2); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -62,7 +62,7 @@ namespace PoweredSoft.DynamicLinq.Fluent | ||||
|             // create a query part. | ||||
|             var part = new QueryBuilderFilter(); | ||||
|             part.And = and; | ||||
|             part.Parts = qb.Filters; | ||||
|             part.Filters = qb.Filters; | ||||
|             Filters.Add(part); | ||||
|              | ||||
|             //return self. | ||||
| @ -154,42 +154,63 @@ namespace PoweredSoft.DynamicLinq.Fluent | ||||
|         protected virtual IQueryable<T> BuildFilters(IQueryable<T> query) | ||||
|         { | ||||
|             if (Filters == null || Filters?.Count() == 0) | ||||
|                 return query;               | ||||
|                 return query; | ||||
| 
 | ||||
|             // shared parameter. | ||||
|             var sharedParameter = Expression.Parameter(typeof(T), "t"); | ||||
| 
 | ||||
|             // build the expression. | ||||
|             var filterExpressionMerged = BuildFilterExpression(sharedParameter, Filters); | ||||
|              | ||||
|             // make changes on the query. | ||||
|             query = query.Where(filterExpressionMerged); | ||||
| 
 | ||||
|             var parameter = Expression.Parameter(typeof(T), "t"); | ||||
|             var expression = BuildFilterExpression(parameter, Filters); | ||||
|             var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter); | ||||
|             query = query.Where(lambda); | ||||
|             return query; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual Expression BuildFilterExpression(ParameterExpression parameter, List<QueryBuilderFilter> filters) | ||||
|         protected virtual Expression<Func<T, bool>> BuildFilterExpression(ParameterExpression parameter, List<QueryBuilderFilter> filters) | ||||
|         { | ||||
|             Expression ret = null; | ||||
|             Expression<Func<T, bool>> temp = null; | ||||
| 
 | ||||
|              | ||||
| 
 | ||||
|             filters.ForEach(filter => | ||||
|             { | ||||
|                 Expression innerExpression; | ||||
|                 if (filter.Parts?.Any() == true) | ||||
|                     innerExpression = BuildFilterExpression(parameter, filter.Parts); | ||||
|                 Expression<Func<T, bool>> innerExpression; | ||||
|                 if (filter.Filters?.Any() == true) | ||||
|                     innerExpression = BuildFilterExpression(parameter, filter.Filters); | ||||
|                 else | ||||
|                     innerExpression = BuildFilterExpression(parameter, filter); | ||||
| 
 | ||||
|                 if (ret != null) | ||||
|                     ret = filter.And ? Expression.And(ret, innerExpression) : Expression.Or(ret, innerExpression); | ||||
|                 if (temp == null) | ||||
|                 { | ||||
|                     temp = innerExpression; | ||||
|                 } | ||||
|                 else | ||||
|                     ret = innerExpression; | ||||
|                 { | ||||
|                     if (filter.And) | ||||
|                         temp = Expression.Lambda<Func<T, bool>>(Expression.And(temp.Body, innerExpression.Body), parameter); | ||||
|                     else | ||||
|                         temp = Expression.Lambda<Func<T, bool>>(Expression.Or(temp.Body, innerExpression.Body), parameter); | ||||
|                 } | ||||
|                      | ||||
|             }); | ||||
| 
 | ||||
|             return temp; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual Expression<Func<T, bool>> BuildFilterExpression(ParameterExpression parameter, QueryBuilderFilter filter) | ||||
|         { | ||||
|             var ret = QueryableHelpers.CreateFilterExpression<T>( | ||||
|                 filter.Path, | ||||
|                 filter.ConditionOperator, | ||||
|                 filter.Value, | ||||
|                 filter.ConvertStrategy, | ||||
|                 filter.CollectionHandling, | ||||
|                 parameter: parameter | ||||
|             ); | ||||
| 
 | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual Expression BuildFilterExpression(ParameterExpression parameter, QueryBuilderFilter filter) | ||||
|         { | ||||
|             var member = QueryableHelpers.ResolvePathForExpression(parameter, filter.Path); | ||||
|             var constant = QueryableHelpers.ResolveConstant(member, filter.Value, filter.ConvertStrategy); | ||||
|             var expression = QueryableHelpers.GetConditionExpressionForMember(parameter, member, filter.ConditionOperator, constant); | ||||
|             return expression; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,6 +13,7 @@ namespace PoweredSoft.DynamicLinq.Fluent | ||||
|         public object Value { get; set; } | ||||
|         public bool And { get; set; } | ||||
|         public QueryConvertStrategy ConvertStrategy { get; set; } | ||||
|         public List<QueryBuilderFilter> Parts { get; set; } = new List<QueryBuilderFilter>(); | ||||
|         public List<QueryBuilderFilter> Filters { get; set; } = new List<QueryBuilderFilter>(); | ||||
|         public QueryCollectionCondition CollectionHandling { get;  set; } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -3,6 +3,7 @@ using System.Collections.Generic; | ||||
| using System.Data.SqlClient; | ||||
| using System.Linq; | ||||
| using System.Linq.Expressions; | ||||
| using System.Reflection; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| 
 | ||||
| @ -117,5 +118,103 @@ namespace PoweredSoft.DynamicLinq.Helpers | ||||
|             query = query.Provider.CreateQuery<T>(resultExpression); | ||||
|             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) | ||||
|         { | ||||
|             var partStr = parts.First(); | ||||
|             var isLast = parts.Count == 1; | ||||
| 
 | ||||
|             // the member expression. | ||||
|             var memberExpression = Expression.PropertyOrField(current, partStr); | ||||
| 
 | ||||
|             // 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"); | ||||
| 
 | ||||
|             // create the expression and return it. | ||||
|             if (isLast) | ||||
|             { | ||||
|                 var constant = QueryableHelpers.ResolveConstant(memberExpression, value, convertStrategy); | ||||
|                 var filterExpression = QueryableHelpers.GetConditionExpressionForMember(parameter, memberExpression, condition, constant); | ||||
|                 var lambda = Expression.Lambda(filterExpression, parameter); | ||||
|                 return lambda; | ||||
|             } | ||||
|              | ||||
|             // TODO special handling for collections. | ||||
|             if (IsEnumerable(memberExpression)) | ||||
|             { | ||||
|                 /* //// first recursion. | ||||
|     //var typeOfClass = typeof(Author); | ||||
|     //var parameter = Expression.Parameter(typeOfClass, "t"); | ||||
|     //var posts = Expression.PropertyOrField(parameter, "Posts"); | ||||
| 
 | ||||
|     //// second recursion | ||||
|     //{ | ||||
|     //    var subListType = posts.Type.GetGenericArguments().First(); | ||||
| 
 | ||||
|     //    var innerParam = Expression.Parameter(subListType, "t2"); | ||||
|     //    var field = Expression.PropertyOrField(innerParam, "Title"); | ||||
|     //    var innerCondition = PoweredSoft.DynamicLinq.Helpers.QueryableHelpers.GetConditionExpressionForMember(innerParam, field, ConditionOperators.Equal, Expression.Constant("Match")); | ||||
|     //    var lambda = Expression.Lambda(innerCondition, innerParam); | ||||
| 
 | ||||
|     //    // any | ||||
|     //    var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "All" && t.GetParameters().Count() == 2); | ||||
|     //    var genericAnyMethod = anyMethod.MakeGenericMethod(subListType); | ||||
|     //    var subExpression = Expression.Call(genericAnyMethod, posts, lambda); | ||||
| 
 | ||||
|     //    var finalLambda = Expression.Lambda<Func<Author, bool>>(subExpression, parameter); | ||||
|     //    query = query.Where(finalLambda); | ||||
|     //} | ||||
|  */ | ||||
| 
 | ||||
|                 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); | ||||
| 
 | ||||
|                 // 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); | ||||
|         } | ||||
| 
 | ||||
|         public static MethodInfo GetCollectionMethod(QueryCollectionCondition collectionHandling) | ||||
|         { | ||||
|             if (collectionHandling == QueryCollectionCondition.All) | ||||
|                 return Constants.AllMethod; | ||||
|             else if (collectionHandling == QueryCollectionCondition.Any) | ||||
|                 return Constants.AnyMethod; | ||||
| 
 | ||||
|             throw new NotSupportedException($"{collectionHandling} is not supported"); | ||||
|         } | ||||
| 
 | ||||
|         public static Expression<Func<T, bool>> CreateFilterExpression<T>(string path,  | ||||
|             ConditionOperators condition,  | ||||
|             object value,  | ||||
|             QueryConvertStrategy convertStrategy,  | ||||
|             QueryCollectionCondition collectionHandling = QueryCollectionCondition.Any, | ||||
|             ParameterExpression parameter = null) | ||||
|         { | ||||
|             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 ret = result as Expression<Func<T, bool>>; | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsEnumerable(MemberExpression member) | ||||
|         { | ||||
|             var ret = member.Type.FullName.StartsWith("System.Collection"); | ||||
|             return ret; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user