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; } }