public override KeyValuePair <string, Expression>?GetExpression <TSource>(Type sourceType, ParameterExpression rootParameter, IEnumerable <TSource> source) { // 1. Parse the instruction. var fieldNames = Parameter.Split(','); var instructions = fieldNames.Select(s => { return(InstructionHelper.ExtractInstruction(s)); }).Where(s => s.HasValue); if (instructions.Count() == 0) { throw new ArgumentException("the parameter cannot be empty"); } if (instructions.Any(i => !_orderByInstructions.Contains(i.Value.Key))) { throw new ArgumentException("At least one instruction is not supported"); } var onInstruction = instructions.FirstOrDefault(i => i.Value.Key == _onInstruction); var orderInstruction = instructions.FirstOrDefault(i => i.Value.Key == _orderInstruction); if (onInstruction == null) { throw new ArgumentException("The 'on' instruction must be specified"); } var fnName = "OrderBy"; if (orderInstruction != null && orderInstruction.Value.Value == "desc") { fnName = "OrderByDescending"; } // 2. Construct the selector. ParameterExpression arg = Expression.Parameter(sourceType, "x"); MemberExpression property = Expression.Property(arg, onInstruction.Value.Value); var selector = Expression.Lambda(property, new ParameterExpression[] { arg }); // 3. Call the order function and return the result. var enumarableType = typeof(Queryable); var method = enumarableType.GetMethods() .Where(m => m.Name == fnName && m.IsGenericMethodDefinition) .Where(m => { var parameters = m.GetParameters().ToList(); return(parameters.Count == 2); }).Single().MakeGenericMethod(sourceType, property.Type); if (SubInstruction != null) { var subExpr = SubInstruction.GetExpression(sourceType, rootParameter, source); var call = Expression.Call(method, subExpr.Value.Value, selector); return(new KeyValuePair <string, Expression>(Name, call)); } return(new KeyValuePair <string, Expression>(Name, Expression.Call(method, rootParameter, selector))); }
public override KeyValuePair <string, Expression>?GetExpression <TSource>(Type outerSourceType, ParameterExpression rootParameter, IEnumerable <TSource> source) { if (outerSourceType == null) { throw new ArgumentNullException(nameof(outerSourceType)); } // 1. Split the value & extract the field names or requests. var splitted = Parameter.Split(','); var instructions = splitted.Select(s => InstructionHelper.ExtractInstruction(s)).Where(s => s.HasValue); if (!parameters.Any(p => instructions.Any(i => i.Value.Key == p))) { throw new ArgumentException("either inner or outer parameter is not specified"); } // 2. Construct the expressions. var outer = instructions.First(i => i.Value.Key == outerKey).Value.Value; var inner = instructions.First(i => i.Value.Key == innerKey).Value.Value; var select = instructions.FirstOrDefault(i => i.Value.Key == selectKey); var selectValue = string.Empty; if (select != null) { selectValue = select.Value.Value; } Type innerSourceType = outerSourceType; Expression targetExpression = null; if (TargetInstruction != null) { var targetArg = Expression.Parameter(typeof(IQueryable <TSource>), "t"); targetExpression = TargetInstruction.GetExpression(outerSourceType, targetArg, source).Value.Value; innerSourceType = targetExpression.Type.GetGenericArguments().First(); } Type tResult = innerSourceType; var fieldNames = outer.GetParameters(); var outerArg = Expression.Parameter(outerSourceType, "x"); var innerArg = Expression.Parameter(innerSourceType, "y"); LambdaExpression outerLambda = null, innerLambda = null; Type resultType; if (fieldNames.Count() == 1) { resultType = outerSourceType.GetProperty(fieldNames.First()).PropertyType; var outerProperty = Expression.Property(outerArg, fieldNames.First()); var innerProperty = Expression.Property(innerArg, fieldNames.First()); outerLambda = Expression.Lambda(outerProperty, new ParameterExpression[] { outerArg }); innerLambda = Expression.Lambda(innerProperty, new ParameterExpression[] { innerArg }); } else { var commonAnonType = ReflectionHelper.CreateNewAnonymousType(outerSourceType, fieldNames); resultType = commonAnonType.AsType(); var outerNewExpr = ExpressionBuilder.BuildNew(outer, outerSourceType, commonAnonType, outerArg); var innerNewExpr = ExpressionBuilder.BuildNew(inner, innerSourceType, commonAnonType, innerArg); outerLambda = Expression.Lambda(outerNewExpr, new ParameterExpression[] { outerArg }); innerLambda = Expression.Lambda(innerNewExpr, new ParameterExpression[] { innerArg }); } LambdaExpression selectorResult = null; if (string.IsNullOrWhiteSpace(selectValue)) { selectorResult = Expression.Lambda(outerArg, new ParameterExpression[] { outerArg, innerArg }); } else { var selectAttributes = new List <KeyValuePair <string, ICollection <string> > >(); foreach (var val in selectValue.Split('|')) { var values = val.Split('$'); if (values.Count() != 2 && values.Count() != 1) { continue; } var key = values.First(); var kvp = selectAttributes.FirstOrDefault(s => s.Key == key); if (kvp.IsEmpty()) { kvp = new KeyValuePair <string, ICollection <string> >(key, new List <string>()); selectAttributes.Add(kvp); } if (values.Count() == 2) { kvp.Value.Add(values.ElementAt(1)); } } if (!selectAttributes.Any(a => selectParameters.Contains(a.Key))) { throw new InvalidOperationException("At least one parameter in select is not supported"); } Dictionary <string, Type> mapping = new Dictionary <string, Type>(); var outerSelect = selectAttributes.FirstOrDefault(a => a.Key == outerKey); var innerSelect = selectAttributes.FirstOrDefault(a => a.Key == innerKey); AddTypes(mapping, "outer", outerSourceType, outerSelect); AddTypes(mapping, "inner", innerSourceType, innerSelect); var anonymousType = ReflectionHelper.CreateNewAnonymousType(mapping); var parameters = new List <Expression>(); var tmp = GetParameterExpressions(outerArg, outerSelect); if (tmp != null) { parameters.AddRange(tmp); } tmp = GetParameterExpressions(innerArg, innerSelect); if (tmp != null) { parameters.AddRange(tmp); } var newExpr = Expression.New(anonymousType.DeclaredConstructors.First(), parameters); selectorResult = Expression.Lambda(newExpr, new ParameterExpression[] { innerArg, outerArg }); tResult = anonymousType.AsType(); } var enumarableType = typeof(Queryable); var method = enumarableType.GetMethods().Where(m => m.Name == "Join" && m.IsGenericMethodDefinition).Where(m => m.GetParameters().ToList().Count == 5).First(); var genericMethod = method.MakeGenericMethod(outerSourceType, innerSourceType, resultType, tResult); MethodCallExpression call = null; if (targetExpression == null) { call = Expression.Call(genericMethod, Expression.Constant(source), Expression.Constant(source), outerLambda, innerLambda, selectorResult); } else { call = Expression.Call(genericMethod, Expression.Constant(source), targetExpression, Expression.Constant(outerLambda), Expression.Constant(innerLambda), selectorResult); } return(new KeyValuePair <string, Expression>(Name, call)); }
public override KeyValuePair <string, Expression>?GetExpression <TSource>(Type sourceType, ParameterExpression rootParameter, IEnumerable <TSource> source) { // Callback used to generate the group by expression. Func <IEnumerable <string>, Type, Type, Expression, MethodCallExpression> getGroupByInst = (names, eType, qType, groupByArg) => { var method = qType.GetMethods() .Where(m => m.Name == "GroupBy" && m.IsGenericMethodDefinition) .Where(m => m.GetParameters().ToList().Count == 2).First(); ParameterExpression arg = Expression.Parameter(eType, "x"); MethodInfo genericMethod = null; LambdaExpression selector = null; if (names.Count() == 1) { var fieldName = names.First(); var propertyInfo = eType.GetProperty(fieldName); MemberExpression keyProperty = Expression.Property(arg, fieldName); var keySelector = Expression.Lambda(keyProperty, new ParameterExpression[] { arg }); genericMethod = method.MakeGenericMethod(eType, propertyInfo.PropertyType); selector = Expression.Lambda(keyProperty, new ParameterExpression[] { arg }); } else { var sourceTypes = names.Select(n => sourceType.GetProperty(n)); var expressions = sourceTypes.Select(s => Expression.Property(arg, s)); var anonType = ReflectionHelper.CreateNewAnonymousType(sourceType, names); var newExpr = Expression.New(anonType.DeclaredConstructors.First(), expressions); selector = Expression.Lambda(newExpr, arg); genericMethod = method.MakeGenericMethod(eType, anonType.AsType()); } return(Expression.Call(genericMethod, groupByArg, selector)); }; if (sourceType == null) { throw new ArgumentNullException(nameof(sourceType)); } Type queryableType = typeof(Queryable), enumerableType = typeof(Enumerable); // 1. Split the value & extract the field names or requests. var splitted = Parameter.Split(','); var instructions = splitted.Select(s => { return(InstructionHelper.ExtractInstruction(s)); }).Where(s => s.HasValue); if (instructions.Count() == 0) { return(null); } if (instructions.Count() != splitted.Count()) { throw new ArgumentException("At least one parameter is not an instruction : <operation>(<parameter>)"); } if (instructions.Any(i => !_groupByInstructions.Contains(i.Value.Key))) { throw new ArgumentException("At least one instruction is not supported"); } var onInstruction = instructions.FirstOrDefault(i => i.Value.Key == _onInstruction); if (onInstruction == null) { throw new ArgumentException("The 'on' instruction must be specified"); } // r var sourceQueryableType = typeof(IQueryable <>).MakeGenericType(sourceType); var sourceEnumerableType = typeof(IEnumerable <>).MakeGenericType(sourceType); var orderedEnumerableType = typeof(IOrderedEnumerable <>).MakeGenericType(sourceType); Expression finalSelectArg = null; if (SubInstruction == null) { finalSelectArg = Expression.Constant(source); } else { finalSelectArg = Expression.Parameter(sourceQueryableType, "r"); } var fieldNames = onInstruction.Value.Value.Split(_fieldSeparator); var groupByInst = getGroupByInst(fieldNames, sourceType, queryableType, finalSelectArg); MethodCallExpression instruction = null; var aggregateInstruction = instructions.FirstOrDefault(i => i.Value.Key == _aggregateInstruction); if (aggregateInstruction != null) { var aggregateInstructionVal = InstructionHelper.ExtractAggregateInstruction(aggregateInstruction.Value.Value); if (aggregateInstructionVal == null) { throw new ArgumentException("the aggregate instruction is not correct"); } var kvp = aggregateInstructionVal.Value; var method = queryableType.GetMethods() .Where(m => m.Name == "Select" && m.IsGenericMethodDefinition) .Where(m => m.GetParameters().ToList().Count == 2).First() .MakeGenericMethod(new Type[] { sourceQueryableType, sourceQueryableType }); var propertyName = kvp.Value; var propertyInfo = sourceType.GetProperty(propertyName); // o var orderArg = Expression.Parameter(sourceType, "o"); // s var selectArg = Expression.Parameter(sourceEnumerableType, "s"); // b var selectFirstArg = Expression.Parameter(sourceEnumerableType, "b"); // o => o.[Value] var keyProperty = Expression.Property(orderArg, propertyName); var orderBySelector = Expression.Lambda(keyProperty, new ParameterExpression[] { orderArg }); // s => s.OrderBy(o => o.[Value]) MethodCallExpression orderByCall = null; if (string.Equals(kvp.Key, "max", StringComparison.CurrentCultureIgnoreCase)) { orderByCall = Expression.Call(enumerableType, "OrderByDescending", new[] { sourceType, propertyInfo.PropertyType }, selectArg, orderBySelector); } else { orderByCall = Expression.Call(enumerableType, "OrderBy", new[] { sourceType, propertyInfo.PropertyType }, selectArg, orderBySelector); } var selectBody = Expression.Lambda(orderByCall, new ParameterExpression[] { selectArg }); var quotedSelectBody = Expression.Quote(selectBody); // z.GroupBy(x => x.FirstName).Select(s => s.OrderBy(o => o.BirthDate)) var selectRequest = Expression.Call(queryableType, "Select", new[] { sourceEnumerableType, orderedEnumerableType }, groupByInst, quotedSelectBody); // b => b.First() var selectFirstRequest = Expression.Call(enumerableType, "First", new[] { sourceType }, selectFirstArg); var selectFirstLambda = Expression.Lambda(selectFirstRequest, new ParameterExpression[] { selectFirstArg }); instruction = Expression.Call(queryableType, "Select", new[] { sourceEnumerableType, sourceType }, selectRequest, selectFirstLambda); } else { instruction = groupByInst; } return(new KeyValuePair <string, Expression>(Name, instruction)); }