protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            // Check if the method is SelectMany and get the collection selector
            if (!QueryExpressionHelper.GetSelectManyCollectionSelector(
                    node,
                    out LambdaExpression collectionSelector))
            {
                return(base.VisitMethodCall(node));
            }

            MethodCallExpression defaultIfEmpty =
                ExpressionHelper.SkipConversionNodes(collectionSelector.Body)
                as MethodCallExpression;

            // Check if the collection selector of the SelectMany is DefaultOrEmpty and
            // get its source
            if (!QueryExpressionHelper.GetDefaultOrEmptySource(defaultIfEmpty, out Expression expr))
            {
                return(base.VisitMethodCall(node));
            }

            MethodCallExpression where = expr as MethodCallExpression;

            // Check if the source of DefaultOrEmpty is Where and get its predicate
            if (!QueryExpressionHelper.GetWherePredicate(where, out LambdaExpression predicate))
            {
                return(base.VisitMethodCall(node));
            }

            // Get the result selector of the original SelectMany expression.
            // It is used to achieve the exact same return type as the original SelectMany
            // expression has.

            if (!(ExpressionHelper.SkipQuoteNode(node.Arguments[2]) is LambdaExpression originalResultSelector))
            {
                return(base.VisitMethodCall(node));
            }

            // Check if the source of the Where expression contains any parameter reference
            // The inner selector of the Join statement does not have parameter, so if the
            // source refers the parameter somewhere, it cannot be used as source
            bool parameterReferred = ExpressionSearchVisitor.Search(
                where.Arguments[0],
                collectionSelector.Parameters[0]);

            if (parameterReferred)
            {
                return(base.VisitMethodCall(node));
            }

            // Analyse the predicate, find key mapping
            EqualityMappingDetector detector =
                new EqualityMappingDetector(
                    collectionSelector.Parameters[0],
                    predicate.Parameters[0]);

            if (!detector.Detect(predicate.Body))
            {
                // Cannot convert the predicate into join
                return(base.VisitMethodCall(node));
            }

            // Everything is appropriate for a conversion, visit the inner and outer sources
            Expression outerSource = this.Visit(node.Arguments[0]);
            Expression innerSource = this.Visit(where.Arguments[0]);

            // Inner and outer entity types
            Type outerType = collectionSelector.Parameters[0].Type;
            Type innerType = predicate.Parameters[0].Type;

            LinqJoinKeyHelper.CreateKeySelectors(
                outerType,
                innerType,
                detector.LeftMembers,
                detector.RightMembers,
                out LambdaExpression outerKey,
                out LambdaExpression innerKey);

            // Create the result selector of the GroupJoin
            LambdaExpression groupJoinResultSelector =
                CreateGroupJoinResultSelector(outerType, innerType);

            Type groupJoinResultType = groupJoinResultSelector.Body.Type;

            // Create the GroupJoin method definition
            // Generic arguments:
            // Outer entity type (same as in SelectMany)
            // Inner entity type (same as in SelectMany)
            // Key type
            // Result type (custom tuple)
            MethodInfo groupJoinMethod =
                QueryMethods.GroupJoin.MakeGenericMethod(
                    node.Method.GetGenericArguments()[0],
                    node.Method.GetGenericArguments()[1],
                    outerKey.Body.Type,
                    groupJoinResultType);

            // Create the Join call expression
            // Arguments:
            // Outer collection (visited source of SelectMany expression)
            // Inner collection (visited source of the Where expression)
            // Outer key selector
            // Inner key selector
            // Result selector (tuple)
            Expression groupJoin = Expression.Call(
                groupJoinMethod,
                outerSource,
                innerSource,
                Expression.Quote(outerKey),
                Expression.Quote(innerKey),
                Expression.Quote(groupJoinResultSelector));

            // Collection selector for the post SelectMany expression
            LambdaExpression postSelectManyCollectionSelector =
                CreatePostSelectManyCollectionSelector(
                    innerType,
                    groupJoinResultType);

            // Result selector for the post SelectMany expression.
            LambdaExpression postSelectManyResultSelector =
                CreatePostSelectManyResultSelector(
                    innerType,
                    groupJoinResultType,
                    originalResultSelector);

            // Define the method of the post SelectMany.
            // Generic arguments:
            // Result type of the GroupJoin
            // Inner entity type
            // Result type (same as the original SelectMany)
            MethodInfo postSelectManyMethod =
                QueryMethods.SelectMany.MakeGenericMethod(
                    groupJoinResultType,
                    node.Method.GetGenericArguments()[1],
                    node.Method.GetGenericArguments()[2]);

            Expression postSelectMany =
                Expression.Call(
                    postSelectManyMethod,
                    groupJoin,
                    Expression.Quote(postSelectManyCollectionSelector),
                    Expression.Quote(postSelectManyResultSelector));

            return(postSelectMany);
        }
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            LambdaExpression collectionSelector = null;

            // Check if the method is SelectMany and get the collection selector
            if (!QueryExpressionHelper.GetSelectManyCollectionSelector(
                node, 
                out collectionSelector))
            {
                return base.VisitMethodCall(node);
            }

            // Unwrap the selector (hopefully Where expression)
            MethodCallExpression where = 
                ExpressionHelper.SkipConversionNodes(collectionSelector.Body) 
                    as MethodCallExpression;

            LambdaExpression predicate = null;

            // Check if the selector of SelectMany is Where and get the predicate
            if (!QueryExpressionHelper.GetWherePredicate(where, out predicate))
            {
                return base.VisitMethodCall(node);
            }

            // Check if the source of the Where expression contains any parameter reference
            // The inner selector of the Join statement does not have parameter, so if the
            // source refers the parameter somewhere, it cannot be used as source
            bool parameterReferred = ExpressionSearchVisitor.Search(
                where.Arguments[0],
                collectionSelector.Parameters[0]);

            if (parameterReferred)
            {
                return base.VisitMethodCall(node);
            }

            // Analyse the predicate, find key mapping
            EqualityMappingDetector detector =
                new EqualityMappingDetector(
                    collectionSelector.Parameters[0],
                    predicate.Parameters[0]);

            if (!detector.Detect(predicate.Body))
            {
                // Cannot convert the predicate into join
                return base.VisitMethodCall(node);
            }

            // Everything is appropriate for a conversion, visit the inner and outer sources
            Expression outerSource = this.Visit(node.Arguments[0]);
            Expression innerSource = this.Visit(where.Arguments[0]);

            // Inner and outer entity types   
            Type outerType = collectionSelector.Parameters[0].Type;
            Type innerType = predicate.Parameters[0].Type;

            // Build the keys
            LambdaExpression outerKey;
            LambdaExpression innerKey;

            LinqJoinKeyHelper.CreateKeySelectors(
                outerType,
                innerType,
                detector.LeftMembers,
                detector.RightMembers,
                out outerKey,
                out innerKey);
            
            // Create the Join method definition:
            // It has the same generic type arguments as the SelectMany
            // Only add the key type (third argument)
            MethodInfo joinMethod = QueryMethods.Join.MakeGenericMethod(
                node.Method.GetGenericArguments()[0],
                node.Method.GetGenericArguments()[1],
                outerKey.Body.Type,
                node.Method.GetGenericArguments()[2]);

            // Create the Join call expression
            // Arguments:
            // Outer collection (visited source of SelectMany expression)
            // Inner collection (visited source of the Where expression)
            // Outer key selector
            // Inner key selector
            // Result selector (same as the SeletMany result selector)
            return Expression.Call(
                joinMethod,
                outerSource,
                innerSource,
                Expression.Quote(outerKey),
                Expression.Quote(innerKey),
                node.Arguments[2]);
        }
Exemplo n.º 3
0
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            // Check if the method is SelectMany and get the collection selector
            if (!QueryExpressionHelper.GetSelectManyCollectionSelector(
                    node,
                    out LambdaExpression collectionSelector))
            {
                return(base.VisitMethodCall(node));
            }

            // Unwrap the selector (hopefully Where expression)
            MethodCallExpression where =
                ExpressionHelper.SkipConversionNodes(collectionSelector.Body)
                as MethodCallExpression;


            // Check if the selector of SelectMany is Where and get the predicate
            if (!QueryExpressionHelper.GetWherePredicate(where, out LambdaExpression predicate))
            {
                return(base.VisitMethodCall(node));
            }

            // Check if the source of the Where expression contains any parameter reference
            // The inner selector of the Join statement does not have parameter, so if the
            // source refers the parameter somewhere, it cannot be used as source
            bool parameterReferred = ExpressionSearchVisitor.Search(
                where.Arguments[0],
                collectionSelector.Parameters[0]);

            if (parameterReferred)
            {
                return(base.VisitMethodCall(node));
            }

            // Analyse the predicate, find key mapping
            EqualityMappingDetector detector =
                new EqualityMappingDetector(
                    collectionSelector.Parameters[0],
                    predicate.Parameters[0]);

            if (!detector.Detect(predicate.Body))
            {
                // Cannot convert the predicate into join
                return(base.VisitMethodCall(node));
            }

            // Everything is appropriate for a conversion, visit the inner and outer sources
            Expression outerSource = this.Visit(node.Arguments[0]);
            Expression innerSource = this.Visit(where.Arguments[0]);

            // Inner and outer entity types
            Type outerType = collectionSelector.Parameters[0].Type;
            Type innerType = predicate.Parameters[0].Type;

            LinqJoinKeyHelper.CreateKeySelectors(
                outerType,
                innerType,
                detector.LeftMembers,
                detector.RightMembers,
                out LambdaExpression outerKey,
                out LambdaExpression innerKey);

            // Create the Join method definition:
            // It has the same generic type arguments as the SelectMany
            // Only add the key type (third argument)
            MethodInfo joinMethod = QueryMethods.Join.MakeGenericMethod(
                node.Method.GetGenericArguments()[0],
                node.Method.GetGenericArguments()[1],
                outerKey.Body.Type,
                node.Method.GetGenericArguments()[2]);

            // Create the Join call expression
            // Arguments:
            // Outer collection (visited source of SelectMany expression)
            // Inner collection (visited source of the Where expression)
            // Outer key selector
            // Inner key selector
            // Result selector (same as the SeletMany result selector)
            return(Expression.Call(
                       joinMethod,
                       outerSource,
                       innerSource,
                       Expression.Quote(outerKey),
                       Expression.Quote(innerKey),
                       node.Arguments[2]));
        }
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            Expression expr = null;
            LambdaExpression collectionSelector = null;

            // Check if the method is SelectMany and get the collection selector
            if (!QueryExpressionHelper.GetSelectManyCollectionSelector(
                node, 
                out collectionSelector))
            {
                return base.VisitMethodCall(node);
            }

            MethodCallExpression defaultIfEmpty = 
                ExpressionHelper.SkipConversionNodes(collectionSelector.Body) 
                    as MethodCallExpression;

            // Check if the collection selector of the SelectMany is DefaultOrEmpty and
            // get its source
            if (!QueryExpressionHelper.GetDefaultOrEmptySource(defaultIfEmpty, out expr))
            {
                return base.VisitMethodCall(node);
            }

            MethodCallExpression where = expr as MethodCallExpression;
            LambdaExpression predicate = null;

            // Check if the source of DefaultOrEmpty is Where and get its predicate
            if (!QueryExpressionHelper.GetWherePredicate(where, out predicate))
            {
                return base.VisitMethodCall(node);
            }

            // Get the result selector of the original SelectMany expression.
            // It is used to achieve the exact same return type as the original SelectMany
            // expression has.
            LambdaExpression originalResultSelector =
                ExpressionHelper.SkipQuoteNode(node.Arguments[2]) as LambdaExpression;

            if (originalResultSelector == null)
            {
                return base.VisitMethodCall(node);
            }

            // Check if the source of the Where expression contains any parameter reference
            // The inner selector of the Join statement does not have parameter, so if the
            // source refers the parameter somewhere, it cannot be used as source
            bool parameterReferred = ExpressionSearchVisitor.Search(
                where.Arguments[0],
                collectionSelector.Parameters[0]);

            if (parameterReferred)
            {
                return base.VisitMethodCall(node);
            }

            // Analyse the predicate, find key mapping
            EqualityMappingDetector detector =
                new EqualityMappingDetector(
                    collectionSelector.Parameters[0],
                    predicate.Parameters[0]);

            if (!detector.Detect(predicate.Body))
            {
                // Cannot convert the predicate into join
                return base.VisitMethodCall(node);
            }

            // Everything is appropriate for a conversion, visit the inner and outer sources
            Expression outerSource = this.Visit(node.Arguments[0]);
            Expression innerSource = this.Visit(where.Arguments[0]);

            // Inner and outer entity types            
            Type outerType = collectionSelector.Parameters[0].Type;
            Type innerType = predicate.Parameters[0].Type;

            // Build the keys
            LambdaExpression outerKey;
            LambdaExpression innerKey;

            LinqJoinKeyHelper.CreateKeySelectors(
                outerType, 
                innerType,
                detector.LeftMembers,
                detector.RightMembers, 
                out outerKey, 
                out innerKey);

            // Create the result selector of the GroupJoin
            LambdaExpression groupJoinResultSelector =
                CreateGroupJoinResultSelector(outerType, innerType);

            Type groupJoinResultType = groupJoinResultSelector.Body.Type;

            // Create the GroupJoin method definition
            // Generic arguments:
            // Outer entity type (same as in SelectMany)
            // Inner entity type (same as in SelectMany)
            // Key type
            // Result type (custom tuple)
            MethodInfo groupJoinMethod = 
                QueryMethods.GroupJoin.MakeGenericMethod(
                    node.Method.GetGenericArguments()[0],
                    node.Method.GetGenericArguments()[1],
                    outerKey.Body.Type,
                    groupJoinResultType);

            // Create the Join call expression
            // Arguments:
            // Outer collection (visited source of SelectMany expression)
            // Inner collection (visited source of the Where expression)
            // Outer key selector
            // Inner key selector
            // Result selector (tuple)
            Expression groupJoin = Expression.Call(
                groupJoinMethod,
                outerSource,
                innerSource,
                Expression.Quote(outerKey),
                Expression.Quote(innerKey),
                Expression.Quote(groupJoinResultSelector));

            // Collection selector for the post SelectMany expression
            LambdaExpression postSelectManyCollectionSelector = 
                CreatePostSelectManyCollectionSelector(
                    innerType, 
                    groupJoinResultType);

            // Result selector for the post SelectMany expression.
            LambdaExpression postSelectManyResultSelector =
                CreatePostSelectManyResultSelector(
                    innerType,
                    groupJoinResultType,
                    originalResultSelector);

            // Define the method of the post SelectMany.
            // Generic arguments:
            // Result type of the GroupJoin
            // Inner entity type
            // Result type (same as the original SelectMany)
            MethodInfo postSelectManyMethod = 
                QueryMethods.SelectMany.MakeGenericMethod(
                    groupJoinResultType,
                    node.Method.GetGenericArguments()[1],
                    node.Method.GetGenericArguments()[2]);

            Expression postSelectMany =
                Expression.Call(
                    postSelectManyMethod,
                    groupJoin,
                    Expression.Quote(postSelectManyCollectionSelector),
                    Expression.Quote(postSelectManyResultSelector));

            return postSelectMany;
        }