示例#1
0
 private IQueryable ToCore(
     Type destinationType,
     object parameters,
     MemberPaths memberPathsToExpand
     ) =>
 _builder
 .GetProjection(
     _source.ElementType,
     destinationType,
     parameters,
     memberPathsToExpand.Select(m => new MemberPath(m)).ToArray()
     )
 .Chain(_source, Select);
        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);

                    var queryExpressions = _mapper.ConfigurationProvider.Internal().ProjectionBuilder.GetProjection
                                           (
                        sourceResult.ElementType,
                        typeof(TDestination),
                        _parameters,
                        _membersToExpand.Select
                        (
                            m => new MemberPath
                            (
                                m.Distinct().ToArray()
                            )
                        ).ToArray()
                                           );

                    destResult = (IQueryable <TDestination>)GetMapExpressions(queryExpressions).Aggregate(sourceResult, Select);
                }
                // case #2: query is arbitrary ("manual") projection
                // example: 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 can safely return it
                else if (IsProjection(resultType, sourceExpression))
                {
                    var sourceResult    = _dataSource.Provider.CreateQuery(sourceExpression);
                    var elementType     = ElementTypeHelper.GetElementType(typeof(TResult));
                    var constructorInfo = typeof(List <>).MakeGenericType(elementType).GetDeclaredConstructor(new Type[0]);
                    if (constructorInfo != null)
                    {
                        var listInstance = (IList)constructorInfo.Invoke(null);
                        foreach (var element in sourceResult)
                        {
                            listInstance.Add(element);
                        }
                        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 queryExpressions = _mapper.ConfigurationProvider.Internal().ProjectionBuilder.GetProjection
                                               (
                            sourceResultType,
                            destResultType,
                            _parameters,
                            _membersToExpand.Select
                            (
                                m => new MemberPath
                                (
                                    m.Distinct().ToArray()
                                )
                            ).ToArray()
                                               );
                        // add projection via "select" operator
                        var expr = GetMapExpressions(queryExpressions).Aggregate(sourceExpression, (source, lambda) => Select(source, lambda));
                        // in case an element operator without predicate expression was found (and thus not replaced)
                        var 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 = 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)System.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;
            }
        }