/// <summary> /// Join using relationship /// </summary> /// <param name="outer"></param> /// <param name="inner"></param> /// <param name="relationship"></param> /// <param name="outerSelector"></param> /// <param name="innerSelector"></param> /// <param name="resultSelector"></param> /// <returns></returns> public static IQueryable <TResult> Join <TOuter, TInner, TRelationship, TOuterEndPoint, TInnerEndPoint, TResult>( [NotNull] this IQueryable <TOuter> outer, [NotNull] IEnumerable <TInner> inner, [NotNull] IEnumerable <TRelationship> relationship, [NotNull] Expression <Func <TOuter, TOuterEndPoint> > outerSelector, [NotNull] Expression <Func <TInner, TInnerEndPoint> > innerSelector, Expression <Func <TOuter, TInner, TRelationship, TResult> > resultSelector ) { Check.NotNull(outer, nameof(outer)); Check.NotNull(inner, nameof(inner)); Check.NotNull(outerSelector, nameof(outerSelector)); Check.NotNull(innerSelector, nameof(innerSelector)); Check.NotNull(resultSelector, nameof(resultSelector)); // grab outer and relationship into the first join var startParameters = new ParameterExpression[] { resultSelector.Parameters.ElementAt(0), resultSelector.Parameters.ElementAt(2) }; var startReturnsParameters = startParameters .Select(p => new KeyValuePair <string, Type>(p.Name, p.Type)) .ToList(); var startReturns = AnonymousTypeBuilder.Create(startReturnsParameters); LambdaExpression start = Expression.Lambda( Expression.New( startReturns.GetConstructor( startReturnsParameters .Select(p => p.Value) .ToArray() ), startParameters ), startParameters ); // grab the returns of the first join and inner into the second join var endLambdaParameters = new ParameterExpression[] { Expression.Parameter(startReturns), resultSelector.Parameters.ElementAt(1) }; Expression end = Expression.Lambda( resultSelector.Body, endLambdaParameters ); // replace using relinq outer and relationship parameter expressions var outerMemberExpression = Expression.PropertyOrField( endLambdaParameters.ElementAt(0), resultSelector.Parameters.ElementAt(0).Name ); var relationshipMemberExpression = Expression.PropertyOrField( endLambdaParameters.ElementAt(0), resultSelector.Parameters.ElementAt(2).Name ); end = ReplacingExpressionVisitor.Replace( resultSelector.Parameters.ElementAt(0), outerMemberExpression, end ); end = ReplacingExpressionVisitor.Replace( resultSelector.Parameters.ElementAt(2), relationshipMemberExpression, end ); // construct method calls var startMethodInfo = Join_TOuter_TInner_TKey_TResult_5( typeof(TOuter), typeof(TRelationship), typeof(TOuterEndPoint), startReturns ); var fakeOuterEndpointSelector = Expression.Lambda( outerSelector.Body, new ParameterExpression[] { Expression.Parameter(typeof(TRelationship)) } ); var startExpression = Expression.Call( null, startMethodInfo, outer.Expression, GetSourceExpression(relationship), Expression.Quote(outerSelector), Expression.Quote(fakeOuterEndpointSelector), Expression.Quote(start) ); var endMethodInfo = Join_TOuter_TInner_TKey_TResult_5( startReturns, typeof(TInner), typeof(TInnerEndPoint), typeof(TResult) ); var fakeInnerEndpointSelector = Expression.Lambda( innerSelector.Body, new ParameterExpression[] { Expression.Parameter(startReturns) } ); var wrapped = Expression.Call( null, endMethodInfo, startExpression, GetSourceExpression(inner), Expression.Quote(fakeInnerEndpointSelector), Expression.Quote(innerSelector), Expression.Quote(end) ); return(outer.Provider.CreateQuery <TResult>(wrapped)); }