Replaces an expression within an expression tree with another expression
Наследование: SqlExpressionVisitor
Пример #1
0
        public virtual IEnumerable <T> ExecuteSubQuery <T>(LambdaExpression query, IDataReader dataReader)
        {
            var projection = (SqlProjectionExpression)query.Body;

            var expectedSelector = "GROUPBYCOLUMNS-" + projection.Select.Alias;

            projection = (SqlProjectionExpression)SqlExpressionReplacer.Replace(projection, c =>
            {
                if (query.Parameters[0] == c)
                {
                    return(Expression.Constant(this));
                }

                var column = c as SqlColumnExpression;

                if (column != null && column.SelectAlias.StartsWith(expectedSelector))
                {
                    var sqlDataTypeProvider = this.SqlDatabaseContext.SqlDataTypeProvider.GetSqlDataType(column.Type);

                    var parameter = Expression.Parameter(typeof(IDataReader));
                    var func      = Expression.Lambda <Func <IDataReader, object> >(Expression.Convert(sqlDataTypeProvider.GetReadExpression(parameter, dataReader.GetOrdinal(column.Name)), typeof(object)), parameter).Compile();

                    var value = func(dataReader);

                    return(Expression.Constant(value, column.Type));
                }

                return(null);
            });

            projection = (SqlProjectionExpression)SqlQueryProvider.Optimize(projection, this.SqlDatabaseContext.SqlDataTypeProvider.GetTypeForEnums(), true);

            return(this.provider.CreateQuery <T>(projection));
        }
Пример #2
0
        protected Expression RewriteSelectManyProjection(MethodCallExpression methodCallExpression)
        {
            var outer          = this.Visit(methodCallExpression.Arguments[0]);
            var collection     = this.Visit(methodCallExpression.Arguments[1]);
            var resultSelector = methodCallExpression.Arguments[2].StripQuotes();

            var originalSelectorA = resultSelector.StripQuotes().Parameters[0];
            var originalSelectorB = resultSelector.StripQuotes().Parameters[1];

            var newA = Expression.Parameter(originalSelectorA.Type);
            var newB = Expression.Parameter(originalSelectorB.Type);

            var resultValue = Expression.Parameter(typeof(ExpandedJoinSelectKey <,>).MakeGenericType(originalSelectorA.Type, originalSelectorB.Type));

            var newResultSelector = Expression.Lambda(Expression.MemberInit(resultValue.Type.CreateNewExpression(), Expression.Bind(resultValue.Type.GetProperty("Outer"), newA), Expression.Bind(resultValue.Type.GetProperty("Inner"), newB)), newA, newB);

            var newSelectMany = Expression.Call(null, MethodInfoFastRef.QueryableSelectManyMethod.MakeGenericMethod(methodCallExpression.Method.GetGenericArguments()[0], methodCallExpression.Method.GetGenericArguments()[1], newResultSelector.ReturnType), outer, collection, newResultSelector);

            var selectorParameter   = Expression.Parameter(resultValue.Type);
            var selectProjectorBody = SqlExpressionReplacer.Replace(resultSelector.Body, originalSelectorA, Expression.Property(selectorParameter, "Outer"));

            selectProjectorBody = SqlExpressionReplacer.Replace(selectProjectorBody, originalSelectorB, Expression.Property(selectorParameter, "Inner"));

            var selectProjector = Expression.Lambda(selectProjectorBody, selectorParameter);

            var select = Expression.Call(null, MethodInfoFastRef.QueryableSelectMethod.MakeGenericMethod(selectorParameter.Type, selectProjector.ReturnType), newSelectMany, selectProjector);

            return(select);
        }
Пример #3
0
        /// <summary>
        /// Translates Joins from type (1) to type (2)
        /// 1) Join(outer, inner, c => c.outerKey, c => d.innerKey, (x, y) => new { x.a.b.c, y.a.b.c })
        /// 2) Join(outer, inner, c => c.outerKey, c => d.innerKey, (x, y) => new { x, y }).Select(c => new { c.x.a.b.c, c.y.a.b.v })
        /// </summary>
        protected Expression RewriteExplicitJoinProjection(MethodCallExpression methodCallExpression)
        {
            var outer            = this.Visit(methodCallExpression.Arguments[0]);
            var inner            = this.Visit(methodCallExpression.Arguments[1]);
            var outerKeySelector = methodCallExpression.Arguments[2].StripQuotes();
            var innerKeySelector = methodCallExpression.Arguments[3].StripQuotes();
            var resultSelector   = methodCallExpression.Arguments[4].StripQuotes();

            var originalOuterKeyParam = resultSelector.StripQuotes().Parameters[0];
            var originalInnerKeyParam = resultSelector.StripQuotes().Parameters[1];

            var outerKey    = Expression.Parameter(outerKeySelector.Parameters[0].Type);
            var innerKey    = Expression.Parameter(innerKeySelector.Parameters[0].Type);
            var resultValue = Expression.Parameter(typeof(ExpandedJoinSelectKey <,>).MakeGenericType(outerKey.Type, innerKey.Type));

            var newResultSelector = Expression.Lambda(Expression.MemberInit(resultValue.Type.CreateNewExpression(), Expression.Bind(resultValue.Type.GetProperty("Outer"), outerKey), Expression.Bind(resultValue.Type.GetProperty("Inner"), innerKey)), outerKey, innerKey);

            var newJoin = Expression.Call(null, MethodInfoFastRef.QueryableJoinMethod.MakeGenericMethod(outer.Type.GetSequenceElementType() ?? outer.Type, inner.Type.GetSequenceElementType() ?? inner.Type, outerKeySelector.ReturnType, newResultSelector.ReturnType), outer, inner, outerKeySelector, innerKeySelector, newResultSelector);

            var selectorParameter   = Expression.Parameter(resultValue.Type);
            var selectProjectorBody = SqlExpressionReplacer.Replace(resultSelector.Body, originalOuterKeyParam, Expression.Property(selectorParameter, "Outer"));

            selectProjectorBody = SqlExpressionReplacer.Replace(selectProjectorBody, originalInnerKeyParam, Expression.Property(selectorParameter, "Inner"));

            var selectProjector = Expression.Lambda(selectProjectorBody, selectorParameter);

            var select = Expression.Call(null, MethodInfoFastRef.QueryableSelectMethod.MakeGenericMethod(selectorParameter.Type, selectProjector.ReturnType), newJoin, selectProjector);

            return(select);
        }
Пример #4
0
        private static MethodCallExpression MakeJoinCallExpression(int index, Expression left, Expression right, PropertyPath targetPath, Dictionary <PropertyPath, int> indexByPath, Dictionary <PropertyPath, Expression> rootExpressionsByPath, Expression sourceParameterExpression)
        {
            Expression leftObject;

            var leftElementType  = left.Type.GetGenericArguments()[0];
            var rightElementType = right.Type.GetGenericArguments()[0];

            var rootPath = targetPath.PathWithoutLast();
            var leftSelectorParameter = Expression.Parameter(leftElementType);

            if (index == 1 && rootExpressionsByPath.ContainsKey(rootPath))
            {
                leftObject = rootExpressionsByPath[rootPath];

                leftObject = SqlExpressionReplacer.Replace(leftObject, c =>
                {
                    if (c == sourceParameterExpression)
                    {
                        return(leftSelectorParameter);
                    }

                    return(null);
                });
            }
            else
            {
                leftObject = CreateExpressionForPath(index - 1, rootPath, leftSelectorParameter, indexByPath);

                if (rootExpressionsByPath.ContainsKey(rootPath))
                {
                    foreach (var property in rootPath)
                    {
                        leftObject = Expression.Property(leftObject, property.Name);
                    }
                }
            }

            var leftSelector = Expression.Lambda(Expression.Property(leftObject, targetPath.Last().Name), leftSelectorParameter);

            var rightSelectorParameter = Expression.Parameter(rightElementType);
            var rightSelector          = Expression.Lambda(rightSelectorParameter, rightSelectorParameter);

            var projector = MakeJoinProjector(leftElementType, rightElementType);

            var method = JoinHelperExtensions.LeftJoinMethod.MakeGenericMethod(leftElementType, rightElementType, targetPath.Last().GetMemberReturnType(), projector.ReturnType);

            return(Expression.Call(null, method, left, right, Expression.Quote(leftSelector), Expression.Quote(rightSelector), Expression.Quote(projector)));
        }
Пример #5
0
        private void BuildProjector(LambdaExpression projectionLambda, LambdaExpression aggregator, out Delegate projector, out Delegate asyncProjector)
        {
            var sqlQueryProviderParam  = Expression.Parameter(typeof(SqlQueryProvider));
            var formatResultsParam     = Expression.Parameter(typeof(SqlQueryFormatResult));
            var placeholderValuesParam = Expression.Parameter(typeof(object[]));

            var elementType = projectionLambda.ReturnType;

            Expression executor;

            if (elementType.IsDataAccessObjectType())
            {
                var concreteElementType = this.DataAccessModel.GetConcreteTypeFromDefinitionType(elementType);

                var constructor = typeof(DataAccessObjectProjector <,>).MakeGenericType(elementType, concreteElementType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();

                executor = Expression.New
                           (
                    constructor,
                    sqlQueryProviderParam,
                    Expression.Constant(this.DataAccessModel),
                    Expression.Constant(this.SqlDatabaseContext),
                    formatResultsParam,
                    placeholderValuesParam,
                    projectionLambda
                           );
            }
            else
            {
                if ((aggregator?.Body as MethodCallExpression)?.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.EnumerableExtensionsAlwaysReadFirstMethod)
                {
                    var constructor = typeof(AlwaysReadFirstObjectProjector <,>).MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();

                    executor = Expression.New
                               (
                        constructor,
                        sqlQueryProviderParam,
                        Expression.Constant(this.DataAccessModel),
                        Expression.Constant(this.SqlDatabaseContext),
                        formatResultsParam,
                        placeholderValuesParam,
                        projectionLambda
                               );
                }
                else
                {
                    var projectorType = !DataAccessObjectAwareResultTypeComparerBuilder.NeedsComparer(projectionLambda.ReturnType) ? typeof(ObjectProjector <,>) : typeof(DataAccessObjectContainerProjector <,>);

                    var constructor = projectorType.MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();

                    executor = Expression.New
                               (
                        constructor,
                        sqlQueryProviderParam,
                        Expression.Constant(this.DataAccessModel),
                        Expression.Constant(this.SqlDatabaseContext),
                        formatResultsParam,
                        placeholderValuesParam,
                        projectionLambda
                               );
                }
            }

            var asyncExecutor     = executor;
            var cancellationToken = Expression.Parameter(typeof(CancellationToken));

            if (aggregator != null)
            {
                var originalExecutor = executor;
                var aggr             = aggregator;
                var newBody          = SqlConstantPlaceholderReplacer.Replace(aggr.Body, placeholderValuesParam);
                executor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor);

                newBody       = ProjectionAsyncRewriter.Rewrite(newBody, cancellationToken);
                asyncExecutor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor);
            }

            projectionLambda = Expression.Lambda(executor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam);
            var asyncProjectorLambda = Expression.Lambda(asyncExecutor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam, cancellationToken);

            ProjectorCacheInfo cacheInfo;
            var key      = new ProjectorCacheKey(projectionLambda);
            var oldCache = this.SqlDatabaseContext.projectorCache;

            if (!oldCache.TryGetValue(key, out cacheInfo))
            {
                cacheInfo.projector      = projectionLambda.Compile();
                cacheInfo.asyncProjector = asyncProjectorLambda.Compile();

                if (this.SqlDatabaseContext.projectorCache.Count >= ProjectorCacheMaxLimit)
                {
                    ProjectionExpressionCacheLogger.Info(() => $"Projector has been flushed because it overflowed with a size of {ProjectionExpressionCacheMaxLimit}\n\nProjector: {projectionLambda}\n\nAt: {new StackTrace()}");

                    var newCache = new Dictionary <ProjectorCacheKey, ProjectorCacheInfo>(ProjectorCacheMaxLimit, ProjectorCacheEqualityComparer.Default);

                    foreach (var value in oldCache.Take(oldCache.Count / 3))
                    {
                        newCache[value.Key] = value.Value;
                    }

                    newCache[key] = cacheInfo;

                    this.SqlDatabaseContext.projectorCache = newCache;
                }
                else
                {
                    var newCache = new Dictionary <ProjectorCacheKey, ProjectorCacheInfo>(oldCache, ProjectorCacheEqualityComparer.Default)
                    {
                        [key] = cacheInfo
                    };

                    this.SqlDatabaseContext.projectorCache = newCache;
                }

                ProjectionCacheLogger.Info(() => $"Cached projector:\n{cacheInfo.projector}");
                ProjectionCacheLogger.Debug(() => $"Projector Cache Size: {this.SqlDatabaseContext.projectionExpressionCache.Count}");
            }

            projector      = cacheInfo.projector;
            asyncProjector = cacheInfo.asyncProjector;
        }
Пример #6
0
        protected Expression RewriteBasicProjection(MethodCallExpression methodCallExpression, bool forSelector)
        {
            Expression[] originalSelectors;
            var          originalSource = methodCallExpression.Arguments[0];
            var          source         = this.Visit(originalSource);
            var          sourceType     = source.Type.GetGenericArguments()[0];
            var          originalPredicateOrSelector = methodCallExpression.Arguments[1];

            if (methodCallExpression.Arguments.Count == 2)
            {
                originalSelectors = new[] { originalPredicateOrSelector };
            }
            else
            {
                originalSelectors = new[] { originalPredicateOrSelector, methodCallExpression.Arguments[2] };
            }

            var sourceParameterExpression = (originalPredicateOrSelector.StripQuotes()).Parameters[0];
            var result = ReferencedRelatedObjectPropertyGatherer.Gather(this.model, originalSelectors, sourceParameterExpression, forSelector);
            var memberAccessExpressionsNeedingJoins = result.ReferencedRelatedObjectByPath;
            var currentRootExpressionsByPath        = result.RootExpressionsByPath;

            var predicateOrSelectors       = result.ReducedExpressions;
            var predicateOrSelectorLambdas = predicateOrSelectors.Select(c => c.StripQuotes()).ToArray();

            if (memberAccessExpressionsNeedingJoins.Count > 0)
            {
                var replacementExpressionForPropertyPath = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default);

                var referencedObjectPaths = memberAccessExpressionsNeedingJoins
                                            .OrderBy(c => c.Key.Length)
                                            .Select(c => c.Value)
                                            .ToList();

                var types = referencedObjectPaths
                            .Select(c => c.FullAccessPropertyPath.Last.PropertyType)
                            .ToList();

                var finalTupleType = CreateFinalTupleType(sourceType, types);
                var replacementExpressionsByPropertyPathForSelector = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default);
                var parameter = Expression.Parameter(finalTupleType);

                var i           = 1;
                var indexByPath = new Dictionary <PropertyPath, int>(PropertyPathEqualityComparer.Default);

                foreach (var value in referencedObjectPaths)
                {
                    indexByPath[value.FullAccessPropertyPath] = i++;
                }

                indexByPath[PropertyPath.Empty] = 0;

                foreach (var x in currentRootExpressionsByPath)
                {
                    indexByPath[x.Key] = 0;
                }

                foreach (var path in referencedObjectPaths.Select(c => c.FullAccessPropertyPath))
                {
                    var replacement = CreateExpressionForPath(referencedObjectPaths.Count, path, parameter, indexByPath);

                    replacementExpressionsByPropertyPathForSelector[path] = replacement;
                }

                replacementExpressionsByPropertyPathForSelector[PropertyPath.Empty] = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, parameter, indexByPath);

                foreach (var value in replacementExpressionsByPropertyPathForSelector)
                {
                    replacementExpressionForPropertyPath[value.Key] = value.Value;
                }

                var propertyPathsByOriginalExpression = referencedObjectPaths
                                                        .SelectMany(d => d.TargetExpressions.Select(e => new { PropertyPath = d.FullAccessPropertyPath, Expression = e }))
                                                        .ToDictionary(c => c.Expression, c => c.PropertyPath);

                foreach (var lambda in predicateOrSelectorLambdas)
                {
                    propertyPathsByOriginalExpression[lambda.Parameters[0]] = PropertyPath.Empty;
                }

                var replacementExpressions = propertyPathsByOriginalExpression
                                             .ToDictionary(c => c.Key, c => replacementExpressionsByPropertyPathForSelector[c.Value]);

                var index       = 1;
                var currentLeft = source;

                foreach (var referencedObjectPath in referencedObjectPaths)
                {
                    var property = referencedObjectPath.FullAccessPropertyPath[referencedObjectPath.FullAccessPropertyPath.Length - 1];
                    var right    = Expression.Constant(this.model.GetDataAccessObjects(property.PropertyType), typeof(DataAccessObjects <>).MakeGenericType(property.PropertyType));

                    var join = MakeJoinCallExpression(index, currentLeft, right, referencedObjectPath.FullAccessPropertyPath, indexByPath, currentRootExpressionsByPath, sourceParameterExpression);

                    currentLeft = join;
                    index++;
                }

                Func <Expression, bool, Expression> replace = null;

                replace = (e, b) => SqlExpressionReplacer.Replace(e, c =>
                {
                    Expression value;

                    if (forSelector && b)
                    {
                        if (result.IncludedPropertyInfoByExpression.ContainsKey(c))
                        {
                            var x = replace(c, false);
                            var y = result.IncludedPropertyInfoByExpression[c];

                            var newList = y.Select(includedPropertyInfo => new IncludedPropertyInfo
                            {
                                RootExpression         = x,
                                FullAccessPropertyPath = includedPropertyInfo.FullAccessPropertyPath,
                                IncludedPropertyPath   = includedPropertyInfo.IncludedPropertyPath
                            }).ToList();

                            this.includedPropertyInfos[x] = newList;

                            return(x);
                        }
                    }

                    if (replacementExpressions.TryGetValue(c, out value))
                    {
                        return(value);
                    }

                    return(null);
                });

                var newPredicatorOrSelectorBodies = predicateOrSelectorLambdas.Select(c => replace(c.Body, true)).ToArray();
                var newPredicateOrSelectors       = newPredicatorOrSelectorBodies.Select(c => Expression.Lambda(c, parameter)).ToArray();

                MethodInfo           newMethod;
                MethodCallExpression newCall;
                var newParameterType = newPredicateOrSelectors[0].Parameters[0].Type;

                if (methodCallExpression.Method.Name.StartsWith("Select") ||
                    methodCallExpression.Method.Name.StartsWith("Where") ||
                    methodCallExpression.Method.Name.EqualsIgnoreCase("OrderBy"))
                {
                    if (methodCallExpression.Method.Name.StartsWith("Select"))
                    {
                        var projectionResultType = newPredicateOrSelectors[0].ReturnType;

                        newMethod = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, projectionResultType);

                        newCall = Expression.Call(null, newMethod, new[]
                        {
                            currentLeft,
                            newPredicateOrSelectors[0]
                        });
                    }
                    else if (methodCallExpression.Method.Name.StartsWith("Where"))
                    {
                        newMethod = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType);

                        newCall = Expression.Call(null, newMethod, new[]
                        {
                            currentLeft,
                            newPredicateOrSelectors[0]
                        });
                    }
                    else if (methodCallExpression.Method.Name.StartsWith("OrderBy"))
                    {
                        var keyType = newPredicateOrSelectors[0].ReturnType;

                        newMethod = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, keyType);

                        newCall = Expression.Call(null, newMethod, new[]
                        {
                            currentLeft,
                            newPredicateOrSelectors[0]
                        });
                    }
                    else
                    {
                        throw new InvalidOperationException();
                    }

                    if (newCall.Method.ReturnType.GetGenericArguments()[0].IsGenericType &&
                        newCall.Method.ReturnType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(LeftRightJoinInfo <,>))
                    {
                        var selectParameter = Expression.Parameter(newCall.Method.ReturnType.GetGenericArguments()[0]);
                        var selectBody      = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, selectParameter, indexByPath);
                        var selectCall      = Expression.Lambda(selectBody, selectParameter);

                        var selectMethod = MethodInfoFastRef.QueryableSelectMethod.MakeGenericMethod
                                           (
                            selectParameter.Type,
                            selectCall.ReturnType
                                           );

                        newCall = Expression.Call(null, selectMethod, new Expression[] { newCall, selectCall });
                    }

                    this.replacementExpressionForPropertyPathsByJoin.Add(new Tuple <Expression, Dictionary <PropertyPath, Expression> >(newCall, replacementExpressionForPropertyPath));
                }
                else if (methodCallExpression.Method.Name == ("GroupBy"))
                {
                    var keyType     = newPredicateOrSelectors[0].ReturnType;
                    var elementType = methodCallExpression.Method.ReturnType.GetGenericArguments()[0].GetGenericArguments()[1];

                    newMethod = methodCallExpression.Method
                                .DeclaringType
                                .GetMethods().Single(c => c.IsGenericMethod &&
                                                     c.GetGenericArguments().Length == 3 &&
                                                     c.GetParameters().Length == 3 &&
                                                     c.GetParameters()[1].ParameterType.IsGenericType &&
                                                     c.GetParameters()[2].ParameterType.IsGenericType &&
                                                     c.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression <>) &&
                                                     c.GetParameters()[2].ParameterType.GetGenericTypeDefinition() == typeof(Expression <>) &&
                                                     c.GetParameters()[1].ParameterType.GetGenericArguments()[0].IsGenericType &&
                                                     c.GetParameters()[2].ParameterType.GetGenericArguments()[0].IsGenericType &&
                                                     c.GetParameters()[1].ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func <,>) &&
                                                     c.GetParameters()[2].ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func <,>))
                                .MakeGenericMethod(newParameterType, keyType, elementType);

                    var elementSelectorParameter = Expression.Parameter(newParameterType);
                    var elementSelectorBody      = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, elementSelectorParameter, indexByPath);
                    var elementSelector          = Expression.Lambda(elementSelectorBody, elementSelectorParameter);

                    newCall = Expression.Call(null, newMethod, new []
                    {
                        currentLeft,
                        newPredicateOrSelectors[0],
                        elementSelector
                    });
                }
                else
                {
                    throw new InvalidOperationException("Method: " + methodCallExpression.Method);
                }

                this.replacementExpressionForPropertyPathsByJoin.Add(new Tuple <Expression, Dictionary <PropertyPath, Expression> >(newCall, replacementExpressionForPropertyPath));

                return(newCall);
            }
            else
            {
                if (source == originalSource &&
                    predicateOrSelectors.SequenceEqual(originalSelectors, ObjectReferenceIdentityEqualityComparer <Expression> .Default))
                {
                    return(methodCallExpression);
                }
                else
                {
                    return(Expression.Call
                           (
                               methodCallExpression.Object,
                               methodCallExpression.Method,
                               predicateOrSelectors.Prepend(source).ToArray()
                           ));
                }
            }
        }
Пример #7
0
        private void BuildProjector(LambdaExpression projectionLambda, LambdaExpression aggregator, Expression <Func <IDataReader, object[]> > keyBuilder, out Delegate projector, out Delegate asyncProjector)
        {
            var sqlQueryProviderParam  = Expression.Parameter(typeof(SqlQueryProvider));
            var formatResultsParam     = Expression.Parameter(typeof(SqlQueryFormatResult));
            var placeholderValuesParam = Expression.Parameter(typeof(object[]));

            var elementType = projectionLambda.ReturnType;

            Expression executor;

            if (elementType.IsDataAccessObjectType())
            {
                var concreteElementType = this.DataAccessModel.GetConcreteTypeFromDefinitionType(elementType);

                var constructor = typeof(DataAccessObjectProjector <,>)
                                  .MakeGenericType(elementType, concreteElementType)
                                  .GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();

                executor = Expression.New
                           (
                    constructor,
                    sqlQueryProviderParam,
                    Expression.Constant(this.DataAccessModel),
                    Expression.Constant(this.SqlDatabaseContext),
                    formatResultsParam,
                    placeholderValuesParam,
                    projectionLambda
                           );
            }
            else
            {
                if ((aggregator?.Body as MethodCallExpression)?.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.EnumerableExtensionsAlwaysReadFirstMethod)
                {
                    var constructor = typeof(AlwaysReadFirstObjectProjector <,>).MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();

                    executor = Expression.New
                               (
                        constructor,
                        sqlQueryProviderParam,
                        Expression.Constant(this.DataAccessModel),
                        Expression.Constant(this.SqlDatabaseContext),
                        formatResultsParam,
                        placeholderValuesParam,
                        projectionLambda
                               );
                }
                else
                {
                    if (keyBuilder == null)
                    {
                        var constructor = typeof(ObjectProjector <,>)
                                          .MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType)
                                          .GetConstructors(BindingFlags.Public | BindingFlags.Instance)
                                          .Single();

                        executor = Expression.New
                                   (
                            constructor,
                            sqlQueryProviderParam,
                            Expression.Constant(this.DataAccessModel),
                            Expression.Constant(this.SqlDatabaseContext),
                            formatResultsParam,
                            placeholderValuesParam,
                            projectionLambda
                                   );
                    }
                    else
                    {
                        var constructor = typeof(ComplexDataAccessObjectProjector <,>)
                                          .MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType)
                                          .GetConstructors(BindingFlags.Public | BindingFlags.Instance)
                                          .Single();

                        executor = Expression.New
                                   (
                            constructor,
                            sqlQueryProviderParam,
                            Expression.Constant(this.DataAccessModel),
                            Expression.Constant(this.SqlDatabaseContext),
                            formatResultsParam,
                            placeholderValuesParam,
                            projectionLambda,
                            keyBuilder
                                   );
                    }
                }
            }

            var asyncExecutor     = executor;
            var cancellationToken = Expression.Parameter(typeof(CancellationToken));

            if (aggregator != null)
            {
                var originalExecutor = executor;
                var aggr             = aggregator;
                var newBody          = SqlConstantPlaceholderReplacer.Replace(aggr.Body, placeholderValuesParam);
                executor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor);

                newBody       = ProjectionAsyncRewriter.Rewrite(newBody, cancellationToken);
                asyncExecutor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor);
            }

            projectionLambda = Expression.Lambda(executor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam);
            var asyncProjectorLambda = Expression.Lambda(asyncExecutor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam, cancellationToken);

            var key      = new ProjectorCacheKey(projectionLambda);
            var oldCache = this.SqlDatabaseContext.projectorCache;

            if (!oldCache.TryGetValue(key, out var cacheInfo))
            {
                cacheInfo.projector      = projectionLambda.Compile();
                cacheInfo.asyncProjector = asyncProjectorLambda.Compile();

                this.SqlDatabaseContext.projectorCache = oldCache.Clone(key, cacheInfo, "projectorCache", this.ProjectorCacheMaxLimit);

                ProjectionCacheLogger.Info(() => $"Cached projector:\n{projectionLambda}");
                ProjectionCacheLogger.Debug(() => $"Projector Cache Size: {this.SqlDatabaseContext.projectionExpressionCache.Count}");
            }

            projector      = cacheInfo.projector;
            asyncProjector = cacheInfo.asyncProjector;
        }
        protected RewriteBasicProjectionResults RewriteBasicProjection(Expression originalSource, Tuple <LambdaExpression, ParameterExpression>[] originalSelectors, bool forProjection)
        {
            var source     = Visit(originalSource);
            var sourceType = source.Type.GetGenericArguments()[0];

            var result = SqlReferencedRelatedObjectPropertyGatherer.Gather(this.model, originalSelectors.Select(c => new Tuple <ParameterExpression, Expression>(c.Item2, c.Item1)).ToList(), forProjection);

            var memberAccessExpressionsNeedingJoins = result.ReferencedRelatedObjects;
            var currentRootExpressionsByPath        = result.RootExpressionsByPath;

            var predicateOrSelectors       = result.ReducedExpressions.Select(c => c.StripQuotes()).ToArray();
            var predicateOrSelectorLambdas = predicateOrSelectors.Select(c => c.StripQuotes()).ToArray();

            if (memberAccessExpressionsNeedingJoins.Count == 0)
            {
                return(new RewriteBasicProjectionResults
                {
                    NewSource = source,
                    NewSelectors = predicateOrSelectors.ToList(),
                    ReferencedObjectPaths = new List <ReferencedRelatedObject>()
                });
            }

            var replacementExpressionForPropertyPath = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default);

            var referencedObjectPaths = memberAccessExpressionsNeedingJoins
                                        .Where(c => !c.IncludedPropertyPath.Any(d => d.PropertyType.IsDataAccessObjectType()))
                                        .OrderBy(c => c.FullAccessPropertyPath.Length)
                                        .Concat
                                        (
                memberAccessExpressionsNeedingJoins
                .Where(c => c.IncludedPropertyPath.Any(d => d.PropertyType.IsDataAccessObjectType()))
                .OrderBy(c => c.FullAccessPropertyPath.Length)
                                        )
                                        .ToList();

            var referencedPathsWithCollectionIncludes = memberAccessExpressionsNeedingJoins
                                                        .Where(c => c.FullAccessPropertyPath.Last.PropertyType.GetGenericTypeDefinitionOrNull() == typeof(RelatedDataAccessObjects <>))
                                                        .Select((c, x) => new { index = x, path = c })
                                                        .OrderBy(c => c.index)
                                                        .ToList();

            var types = referencedObjectPaths
                        .Select(c => c.FullAccessPropertyPath.Last.PropertyType.JoinedObjectType())
                        .ToList();

            var finalTupleType = CreateFinalTupleType(sourceType, types);
            var replacementExpressionsByPropertyPathForSelector = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default);
            var parameter = Expression.Parameter(finalTupleType);

            var i           = 1;
            var indexByPath = new Dictionary <PropertyPath, int>(PropertyPathEqualityComparer.Default);

            foreach (var value in referencedObjectPaths)
            {
                indexByPath[value.FullAccessPropertyPath] = i++;
            }

            indexByPath[PropertyPath.Empty] = 0;

            foreach (var x in currentRootExpressionsByPath)
            {
                indexByPath[x.Key] = 0;
            }

            foreach (var path in referencedObjectPaths.Select(c => c.FullAccessPropertyPath))
            {
                var replacement = CreateExpressionForPath(referencedObjectPaths.Count, path, parameter, indexByPath);

                replacementExpressionsByPropertyPathForSelector[path] = replacement;
            }

            replacementExpressionsByPropertyPathForSelector[PropertyPath.Empty] = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, parameter, indexByPath);

            foreach (var value in replacementExpressionsByPropertyPathForSelector)
            {
                replacementExpressionForPropertyPath[value.Key] = value.Value;
            }

            var propertyPathsByOriginalExpression = referencedObjectPaths
                                                    .SelectMany(d => d.TargetExpressions.Select(e => new { PropertyPath = d.FullAccessPropertyPath, Expression = e }))
                                                    .ToDictionary(c => c.Expression, c => c.PropertyPath);

            foreach (var lambda in predicateOrSelectorLambdas.Select((c, idx) => new { index = idx, value = c }))
            {
                var parameterLocation = originalSelectors[lambda.index].Item1.Parameters.IndexOf(originalSelectors[lambda.index].Item2);

                propertyPathsByOriginalExpression[lambda.value.Parameters[parameterLocation]] = PropertyPath.Empty;
            }

            var replacementExpressions = propertyPathsByOriginalExpression
                                         .ToDictionary(c => c.Key, c => replacementExpressionsByPropertyPathForSelector[c.Value]);

            var index       = 1;
            var currentLeft = source;

            foreach (var referencedObjectPath in referencedObjectPaths)
            {
                var property = referencedObjectPath.FullAccessPropertyPath[referencedObjectPath.FullAccessPropertyPath.Length - 1];
                var right    = Expression.Constant(this.model.GetDataAccessObjects(property.PropertyType.JoinedObjectType()), typeof(DataAccessObjects <>).MakeGenericType(property.PropertyType.JoinedObjectType()));

                var join = MakeJoinCallExpression(index, currentLeft, right, referencedObjectPath.FullAccessPropertyPath, indexByPath, currentRootExpressionsByPath, referencedObjectPath.SourceParameterExpression);

                currentLeft = join;
                index++;
            }

            Expression Replace(Expression e, bool b) => SqlExpressionReplacer.Replace(e, c =>
            {
                if (forProjection && b)
                {
                    if (result.IncludedPropertyInfoByExpression.ContainsKey(c))
                    {
                        var x = Replace(c, false);
                        var y = result.IncludedPropertyInfoByExpression[c];

                        var newList = y.Select(includedPropertyInfo => new IncludedPropertyInfo
                        {
                            RootExpression         = x,
                            FullAccessPropertyPath = includedPropertyInfo.FullAccessPropertyPath,
                            IncludedPropertyPath   = includedPropertyInfo.IncludedPropertyPath
                        }).ToList();

                        this.includedPropertyInfos[x] = newList;

                        return(x);
                    }
                }

                return(replacementExpressions.GetValueOrDefault(c));
            });

            var newPredicatorOrSelectorBodies = predicateOrSelectorLambdas
                                                .Select(c => Replace(c.Body, true))
                                                .ToList();

            var newPredicateOrSelectors = newPredicatorOrSelectorBodies
                                          .Zip(originalSelectors, (x, y) => new { body = x, parameters = y.Item1.Parameters, sourceParameter = y.Item2 })
                                          .Select(c => Expression.Lambda(c.body, c.parameters.Select(d => d == c.sourceParameter ? parameter : c.sourceParameter)))
                                          .ToList();

            return(new RewriteBasicProjectionResults(true)
            {
                NewSource = currentLeft,
                IndexByPath = indexByPath,
                NewSelectors = newPredicateOrSelectors,
                ReferencedObjectPaths = referencedObjectPaths,
                ReplacementExpressionsByPropertyPath = replacementExpressionForPropertyPath
            });
        }
        protected Expression RewriteBasicProjection(MethodCallExpression methodCallExpression, bool forSelector)
        {
            MethodCallExpression newCall = null;

            var selectors = methodCallExpression
                            .Arguments
                            .Where(c => c.Type.GetGenericTypeDefinitionOrNull() == typeof(Expression <>) || c.Type == typeof(LambdaExpression[]))
                            .SelectMany(c => c.Type != typeof(LambdaExpression[]) ? new [] { Visit(c).StripQuotes() } : ((LambdaExpression[])c.StripAndGetConstant().Value).Select(Visit).Cast <LambdaExpression>())
                            .ToArray();

            var result = RewriteBasicProjection(methodCallExpression.Arguments[0], selectors.Select(c => new Tuple <LambdaExpression, ParameterExpression>(c, c.Parameters[0])).ToArray(), forSelector);

            if (!result.Changed)
            {
                if (result.NewSource == methodCallExpression.Arguments[0] && result.NewSelectors.SequenceEqual(selectors, ObjectReferenceIdentityEqualityComparer <Expression> .Default))
                {
                    return(base.VisitMethodCall(methodCallExpression));
                }
            }

            switch (methodCallExpression.Method.Name)
            {
            case "Select":
            {
                var projectionResultType      = result.NewSelectors[0].ReturnType;
                var newParameterType          = result.NewSelectors[0].Parameters[0].Type;
                var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, projectionResultType);

                newCall = Expression.Call(methodWithElementSelector, new[]
                    {
                        result.NewSource,
                        result.NewSelectors[0]
                    });

                break;
            }

            case "SelectMany":
            {
                if (result.NewSelectors.Count == 1)
                {
                    var projectionResultType      = result.NewSelectors[0].ReturnType.GetSequenceElementType();
                    var newParameterType          = result.NewSelectors[0].Parameters[0].Type;
                    var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, projectionResultType);

                    newCall = Expression.Call(methodWithElementSelector, new[]
                        {
                            result.NewSource,
                            result.NewSelectors[0]
                        });
                }
                else
                {
                    var collectionType            = result.NewSelectors[0].ReturnType.GetSequenceElementType();
                    var newParameterType          = result.NewSelectors[0].Parameters[0].Type;
                    var projectionResultType      = result.NewSelectors[1].ReturnType;
                    var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, collectionType, projectionResultType);

                    newCall = Expression.Call(methodWithElementSelector, new[]
                        {
                            result.NewSource,
                            result.NewSelectors[0],
                            result.NewSelectors[1]
                        });
                }

                break;
            }

            case "Where":
            {
                var newParameterType          = result.NewSelectors[0].Parameters[0].Type;
                var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType);

                newCall = Expression.Call
                              (methodWithElementSelector, new[]
                    {
                        result.NewSource,
                        result.NewSelectors[0]
                    });

                break;
            }

            case nameof(QueryableExtensions.OrderByThenBysHelper):
            {
                var resultType                = result.NewSelectors.Count > 0 ? result.NewSelectors[0].ReturnType : null;
                var newParameterType          = result.NewSource.Type.GetSequenceElementType();
                var method                    = methodCallExpression.Method.GetGenericMethodDefinition();
                var methodWithElementSelector = method.GetGenericArguments().Length == 1
                                                ? method.MakeGenericMethod(newParameterType)
                                                : method.MakeGenericMethod(newParameterType, resultType);

                var newSource = result.NewSource;

                newCall = Expression.Call
                          (
                    null,
                    methodWithElementSelector,
                    new[] { newSource, Expression.Constant(result.NewSelectors.ToArray()), methodCallExpression.Arguments[2] }
                          );

                break;
            }

            case "GroupBy":
            {
                var keyType          = result.NewSelectors[0].ReturnType;
                var newParameterType = result.NewSelectors[0].Parameters[0].Type;
                var elementType      = methodCallExpression.Method.ReturnType.GetGenericArguments()[0].GetGenericArguments()[1];

                var methodWithElementSelector = MethodInfoFastRef
                                                .QueryableGroupByWithElementSelectorMethod
                                                .MakeGenericMethod(newParameterType, keyType, elementType);

                if (methodCallExpression.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.QueryableGroupByMethod)
                {
                    if (result.Changed)
                    {
                        var elementSelectorParameter = Expression.Parameter(newParameterType);
                        var elementSelectorBody      = CreateExpressionForPath(result.ReferencedObjectPaths.Count, PropertyPath.Empty, elementSelectorParameter, result.IndexByPath);
                        var elementSelector          = Expression.Lambda(elementSelectorBody, elementSelectorParameter);

                        newCall = Expression.Call(methodWithElementSelector, result.NewSource, result.NewSelectors[0], elementSelector);
                    }
                    else
                    {
                        newCall = Expression.Call(methodCallExpression.Method, result.NewSource, result.NewSelectors[0]);
                    }
                }
                else if (methodCallExpression.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.QueryableGroupByWithElementSelectorMethod)
                {
                    if (result.Changed)
                    {
                        var existingElementSelector  = methodCallExpression.Arguments[2].StripQuotes();
                        var elementSelectorParameter = Expression.Parameter(newParameterType);
                        var pathExpression           = CreateExpressionForPath(result.ReferencedObjectPaths.Count, PropertyPath.Empty, elementSelectorParameter, result.IndexByPath);
                        var newBody = SqlExpressionReplacer.Replace(existingElementSelector.Body, existingElementSelector.Parameters[0], pathExpression);

                        var elementSelector = Expression.Lambda(newBody, elementSelectorParameter);

                        newCall = Expression.Call(methodWithElementSelector, result.NewSource, result.NewSelectors[0], elementSelector);
                    }
                    else
                    {
                        newCall = Expression.Call(methodCallExpression.Method, result.NewSource, result.NewSelectors[0], result.NewSelectors[1]);
                    }
                }
                else
                {
                    throw new NotSupportedException($"Unsupport method when using explicit joins: {methodCallExpression.Method}");
                }

                break;
            }

            default:
            {
                var resultType                = result.NewSelectors.Count > 0 ? result.NewSelectors[0].ReturnType : null;
                var newParameterType          = result.NewSource.Type.GetSequenceElementType();
                var method                    = methodCallExpression.Method.GetGenericMethodDefinition();
                var methodWithElementSelector = method.GetGenericArguments().Length == 1
                                                ? method.MakeGenericMethod(newParameterType)
                                                : method.MakeGenericMethod(newParameterType, resultType);

                newCall = Expression.Call
                          (
                    null,
                    methodWithElementSelector,
                    result.NewSelectors.Count > 0 ? new[] { result.NewSource, result.NewSelectors[0] } : new[] { result.NewSource }
                          );

                break;
            }
            }

            this.replacementExpressionForPropertyPathsByJoin.Add(new Pair <Expression, Dictionary <PropertyPath, Expression> >(newCall, result.ReplacementExpressionsByPropertyPath));

            return(Reselect(newCall, result.ReferencedObjectPaths, result.IndexByPath));
        }
        private MethodCallExpression MakeJoinCallExpression(int index, Expression left, Expression right, PropertyPath targetPath, Dictionary <PropertyPath, int> indexByPath, Dictionary <PropertyPath, Expression> rootExpressionsByPath, Expression sourceParameterExpression)
        {
            Expression leftObject;

            var leftElementType  = left.Type.GetGenericArguments()[0];
            var rightElementType = right.Type.GetGenericArguments()[0];

            var rootPath = targetPath.RemoveLast();
            var leftSelectorParameter = Expression.Parameter(leftElementType);

            if (index == 1 && rootExpressionsByPath.ContainsKey(rootPath))
            {
                leftObject = rootExpressionsByPath[rootPath];

                leftObject = SqlExpressionReplacer.Replace(leftObject, c => c == sourceParameterExpression ? leftSelectorParameter : null);
            }
            else
            {
                leftObject = CreateExpressionForPath(index - 1, rootPath, leftSelectorParameter, indexByPath);

                if (rootExpressionsByPath.ContainsKey(rootPath))
                {
                    foreach (var property in rootPath)
                    {
                        leftObject = Expression.Property(leftObject, property.Name);
                    }
                }
            }

            LambdaExpression leftSelector, rightSelector;

            var projector = MakeJoinProjector(leftElementType, rightElementType);

            if (targetPath.Last.GetMemberReturnType().GetGenericTypeDefinitionOrNull() == typeof(RelatedDataAccessObjects <>))
            {
                leftSelector = Expression.Lambda(leftObject, leftSelectorParameter);

                var typeDescriptor = this.model.TypeDescriptorProvider.GetTypeDescriptor(leftObject.Type);
                var relationship   = typeDescriptor
                                     .GetRelationshipInfos()
                                     .Where(c => c.RelationshipType == RelationshipType.ParentOfOneToMany)
                                     .Single(c => c.ReferencingProperty.PropertyName == targetPath.Last.Name);

                var rightSelectorParameter = Expression.Parameter(rightElementType);
                rightSelector = Expression.Lambda(Expression.Property(rightSelectorParameter, relationship.TargetProperty), rightSelectorParameter);
            }
            else
            {
                leftSelector = Expression.Lambda(Expression.Property(leftObject, targetPath.Last().Name), leftSelectorParameter);

                var rightSelectorParameter = Expression.Parameter(rightElementType);
                rightSelector = Expression.Lambda(rightSelectorParameter, rightSelectorParameter);
            }

            var method = MethodInfoFastRef
                         .QueryableExtensionsLeftJoinMethod
                         .MakeGenericMethod(leftElementType, rightElementType, leftSelector.ReturnType, projector.ReturnType);

            var retval = Expression.Call(method, left, right, Expression.Quote(leftSelector), Expression.Quote(rightSelector), Expression.Quote(projector));

            return(retval);
        }