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) { // 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])); }