Exemplo n.º 1
0
 private static IQueryable Select(IQueryable source, LambdaExpression lambda) => source.Provider.CreateQuery(
     Call(
         null,
         QueryableSelectMethod.MakeGenericMethod(source.ElementType, lambda.ReturnType),
         new[] { source.Expression, Expression.Quote(lambda) }
         )
     );
 private static Expression Select(Expression source, LambdaExpression lambda)
 {
     return(Expression.Call(
                null,
                QueryableSelectMethod.MakeGenericMethod(lambda.Parameters[0].Type, lambda.ReturnType),
                new[] { source, Expression.Quote(lambda) }
                ));
 }
        public TResult Execute <TResult>(Expression expression)
        {
            try
            {
                var resultType = typeof(TResult);
                Inspector.StartQueryExecuteInterceptor(resultType, expression);

                var sourceExpression = ConvertDestinationExpressionToSourceExpression(expression);

                var destResultType   = typeof(TResult);
                var sourceResultType = CreateSourceResultType(destResultType);

                object destResult = null;

                // case #1: query is a projection from complex Source to complex Destination
                // example: users.UseAsDataSource().For<UserDto>().Where(x => x.Age > 20).ToList()
                if (IsProjection <TDestination>(resultType))
                {
                    // in case of a projection, we need an IQueryable
                    var sourceResult = _dataSource.Provider.CreateQuery(sourceExpression);
                    Inspector.SourceResult(sourceExpression, sourceResult);

                    destResult = new ProjectionExpression((IQueryable <TSource>)sourceResult, _mapper.ConfigurationProvider.ExpressionBuilder).To <TDestination>(_parameters, _membersToExpand);
                }
                // case #2: query is arbitrary ("manual") projection
                // exaple: users.UseAsDataSource().For<UserDto>().Select(user => user.Age).ToList()
                // in case an arbitrary select-statement is enumerated, we do not need to map the expression at all
                // and cann safely return it
                else if (IsProjection(resultType, sourceExpression))
                {
                    var sourceResult    = _dataSource.Provider.CreateQuery(sourceExpression);
                    var enumerator      = sourceResult.GetEnumerator();
                    var elementType     = TypeHelper.GetElementType(typeof(TResult));
                    var constructorInfo = typeof(List <>).MakeGenericType(elementType).GetDeclaredConstructor(new Type[0]);
                    if (constructorInfo != null)
                    {
                        var listInstance = (IList)constructorInfo.Invoke(null);
                        while (enumerator.MoveNext())
                        {
                            listInstance.Add(enumerator.Current);
                        }
                        destResult = listInstance;
                    }
                }
                // case #2: projection to simple type
                // example: users.UseAsDataSource().For<UserDto>().FirstOrDefault(user => user.Age > 20)
                else
                {
                    /*
                     *  in case of an element result (so instead of IQueryable<TResult>, just TResult)
                     *  we still want to support parameters.
                     *  This is e.g. the case, when the developer writes "UseAsDataSource().For<TResult>().FirstOrDefault(x => ...)
                     *  To still be able to support parameters, we need to create a query from it.
                     *  That said, we need to replace the "element" operator "FirstOrDefault" with a "Where" operator, then apply our "Select"
                     *  to map from TSource to TResult and finally re-apply the "element" operator ("FirstOrDefault" in our case) so only
                     *  one element is returned by our "Execute<TResult>" method. Otherwise we'd get an InvalidCastException
                     *
                     * So first we visit the sourceExpression and replace "element operators" with "where"
                     * then we create our mapping expression from TSource to TDestination (select) and apply it
                     * finally, we search for the element expression overload of our replaced "element operator" that has no expression as parameter
                     *      this expression is not needed anymore as it has already been applied to the "Where" operation and can be safely omitted
                     * when we're done creating our new expression, we call the underlying provider to execute it
                     */

                    // as we need to replace the innermost element of the expression,
                    // we need to traverse it first in order to find the node to replace or potential caveats
                    // e.g. .UseAsDataSource().For<Dto>().Select(e => e.Name).First()
                    //      in the above case we cannot map anymore as the "select" operator overrides our mapping.
                    var searcher = new ReplaceableMethodNodeFinder <TSource>();
                    searcher.Visit(sourceExpression);
                    // provide the replacer with our found MethodNode or <null>
                    var replacer = new MethodNodeReplacer <TSource>(searcher.MethodNode);

                    // default back to simple mapping of object instance for backwards compatibility (e.g. mapping non-nullable to nullable fields)
                    sourceExpression = replacer.Visit(sourceExpression);

                    if (replacer.FoundElementOperator)
                    {
                        /*  in case of primitive element operators (e.g. Any(), Sum()...)
                         *  we need to map the originating types (e.g. Entity to Dto) in this query
                         *  as the final value will be projected automatically
                         *
                         *  == example 1 ==
                         *  UseAsDataSource().For<Dto>().Any(entity => entity.Name == "thename")
                         *  ..in that case sourceResultType and destResultType would both be "Boolean" which is not mappable for AutoMapper
                         *
                         *  == example 2 ==
                         *  UseAsDataSource().For<Dto>().FirstOrDefault(entity => entity.Name == "thename")
                         *  ..in this case the sourceResultType would be Entity and the destResultType Dto, so we can map the query directly
                         */

                        if (sourceResultType == destResultType)// && destResultType.IsPrimitive)
                        {
                            sourceResultType = typeof(TSource);
                            destResultType   = typeof(TDestination);
                        }

                        var membersToExpand = _membersToExpand.SelectMany(m => m).Distinct().ToArray();
                        var mapExpr         = _mapper.ConfigurationProvider.ExpressionBuilder.CreateMapExpression(sourceResultType, destResultType,
                                                                                                                  _parameters, membersToExpand);
                        // add projection via "select" operator
                        var expr = Expression.Call(
                            null,
                            QueryableSelectMethod.MakeGenericMethod(sourceResultType, destResultType),
                            new[] { sourceExpression, Expression.Quote(mapExpr) }
                            );

                        // in case an element operator without predicate expression was found (and thus not replaced)
                        MethodInfo replacementMethod = replacer.ElementOperator;
                        // in case an element operator with predicate expression was replaced
                        if (replacer.ReplacedMethod != null)
                        {
                            // find replacement method that has no more predicates
                            replacementMethod = typeof(Queryable).GetAllMethods()
                                                .Single(m => m.Name == replacer.ReplacedMethod.Name
#if NET45
                                                        && m.GetParameters().All(p => typeof(Queryable).IsAssignableFrom(p.Member.ReflectedType))
#endif
                                                        && m.GetParameters().Length == replacer.ReplacedMethod.GetParameters().Length - 1);
                        }

                        // reintroduce element operator that was replaced with a "where" operator to make it queryable
                        expr = Expression.Call(null,
                                               replacementMethod.MakeGenericMethod(destResultType), expr);

                        destResult = _dataSource.Provider.Execute(expr);
                    }
                    // If there was no element operator that needed to be replaced by "where", just map the result
                    else
                    {
                        var sourceResult = _dataSource.Provider.Execute(sourceExpression);
                        Inspector.SourceResult(sourceExpression, sourceResult);
                        destResult = _mapper.Map(sourceResult, sourceResultType, destResultType);
                    }
                }

                Inspector.DestResult(destResult);

                // implicitly convert types in case of valuetypes which cannot be casted explicitly
                if (typeof(TResult).IsValueType() && destResult.GetType() != typeof(TResult))
                {
                    return((TResult)Convert.ChangeType(destResult, typeof(TResult)));
                }

                // if it is not a valuetype, we can safely cast it
                return((TResult)destResult);
            }
            catch (Exception x)
            {
                _exceptionHandler(x);
                throw;
            }
        }