using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; // ReSharper disable MemberCanBePrivate.Global namespace FormulaEnginePoC.DynamicExpressionParse { /// /// Microsoft provided class. It allows dynamic string based querying. /// Very handy when, at compile time, you don't know the type of queries that will be generated. /// internal static class DynamicQueryable { #region IQueryable Extensions public static IQueryable Where(this IQueryable source, string predicate, params object[] values) { return (IQueryable)Where((IQueryable)source, predicate, values); } public static IQueryable Where(this IQueryable source, string predicate, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); var lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); } public static IQueryable Select(this IQueryable source, string selector, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); var lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Select", new[] { source.ElementType, lambda.Body.Type }, source.Expression, Expression.Quote(lambda))); } public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) { return (IQueryable)OrderBy((IQueryable)source, ordering, values); } public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); var parameters = new[] { Expression.Parameter(source.ElementType, "") }; var parser = new ExpressionParser(parameters, ordering, values); var orderings = parser.ParseOrdering(); var queryExpr = source.Expression; var methodAsc = "OrderBy"; var methodDesc = "OrderByDescending"; foreach (var o in orderings) { queryExpr = Expression.Call( typeof(Queryable), o.Ascending ? methodAsc : methodDesc, new[] { source.ElementType, o.Selector.Type }, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters))); methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } return source.Provider.CreateQuery(queryExpr); } public static IQueryable Take(this IQueryable source, int count) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Take", new[] { source.ElementType }, source.Expression, Expression.Constant(count))); } public static IQueryable Skip(this IQueryable source, int count) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Skip", new[] { source.ElementType }, source.Expression, Expression.Constant(count))); } public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); var keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values); var elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "GroupBy", new[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type }, source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))); } public static bool Any(this IQueryable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return (bool)source.Provider.Execute( Expression.Call( typeof(Queryable), "Any", new[] { source.ElementType }, source.Expression)); } public static int Count(this IQueryable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return (int)source.Provider.Execute( Expression.Call( typeof(Queryable), "Count", new[] { source.ElementType }, source.Expression)); } public static IQueryable Distinct(this IQueryable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Distinct", new[] { source.ElementType }, source.Expression)); } /// /// Dynamically runs an aggregate function on the IQueryable. /// /// The IQueryable data source. /// The name of the function to run. Can be Sum, Average, Min, Max. /// The name of the property to aggregate over. /// The value of the aggregate function run over the specified property. public static object Aggregate(this IQueryable source, string function, string member) { if (source == null) throw new ArgumentNullException(nameof(source)); if (member == null) throw new ArgumentNullException(nameof(member)); // Properties var property = source.ElementType.GetProperty(member) ?? throw new ArgumentException($"Property [{member}] not found", nameof(member)); var parameter = Expression.Parameter(source.ElementType, "s"); Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter); // We've tried to find an expression of the type Expression>, // which is expressed as ( (TSource s) => s.Price ); //var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function); // Method var aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault( m => m.Name == function && m.ReturnType == property.PropertyType // should match the type of the property && m.IsGenericMethod); // Sum, Average if (aggregateMethod != null) { return source.Provider.Execute( Expression.Call( null, aggregateMethod.MakeGenericMethod(source.ElementType), new[] { source.Expression, Expression.Quote(selector) })); } // Min, Max else { aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault( m => m.Name == function && m.GetGenericArguments().Length == 2 && m.IsGenericMethod); if (null == aggregateMethod) throw new ArgumentException(); return source.Provider.Execute( Expression.Call( null, aggregateMethod.MakeGenericMethod(source.ElementType, property.PropertyType), new[] { source.Expression, Expression.Quote(selector) })); } } #endregion IQueryable Extensions #region IEnumerable Extensions public static IEnumerable Where(this IEnumerable source, string predicate, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Where(predicate, values); } public static IEnumerable Where(this IEnumerable source, string predicate, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Where(predicate, values); } public static IEnumerable Select(this IEnumerable source, string selector, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Select(selector, values); } public static IEnumerable OrderBy(this IEnumerable source, string ordering, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().OrderBy(ordering, values); } public static IEnumerable OrderBy(this IEnumerable source, string ordering, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().OrderBy(ordering, values); } public static IEnumerable Take(this IEnumerable source, int count) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Take(count); } public static IEnumerable Skip(this IEnumerable source, int count) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Skip(count); } public static IEnumerable GroupBy(this IEnumerable source, string keySelector, string elementSelector, params object[] values) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().GroupBy(keySelector, elementSelector, values); } public static bool Any(this IEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Any(); } public static int Count(this IEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Count(); } public static IEnumerable Distinct(this IEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.AsQueryable().Distinct(); } #endregion IEnumerable Extensions } }