title: Trying to implement a LeftJoin extension method to work with EF Core 2.0
body: <p>I am trying to have an extension method to implement left outer join which returns <code>IQueryable</code> that run against EF Core 2.0 data context.</p>
  
  <p>I read the Stack Overflow thread here for help: <a href="https://stackoverflow.com/questions/21615693/extension-method-for-iqueryable-left-outer-join-using-linq">Extension method for IQueryable left outer join using LINQ</a>.  The accepted answer had some problems that were addressed in a later answer <a href="https://stackoverflow.com/a/35414717/3914623">here</a> by Jan Van der Haegen.  I attempted to use the LeftJoin extension method described there with an EF Core 2.0 data context and ran into an exception which I cannot seem to resolve.</p>
  
  <p>As I cannot find any fault with the implementation of the LeftJoin extension method, I attempted to run the same method against a data context using EF6 and it worked as expected. I have shared my experiment including the SQL scripts to generate the test data here: </p>
  
  <p><a href="https://github.com/hewasud/Experiment_20171002" rel="nofollow noreferrer">Experiment with LeftJoin extension run against EF6 and EF Core 2.0</a></p>
  
  <p>It contains two projects one running LeftJoin against EF6 and the other running it against EF Core 2.0.  The extension method is shared via a .Net Standard 2.0 class library.</p>
  
  <p>The extension method for LeftJoin is as follows:</p>
  
  <pre><code>namespace QueryableExtensions
  {
      // Much of the code copied from following URL:
      // https://stackoverflow.com/questions/21615693/extension-method-for-iqueryable-left-outer-join-using-linq
      internal class KeyValuePairHolder&lt;T1, T2&gt;
      {
          public T1 Item1 { get; set; }
          public T2 Item2 { get; set; }
      }
  
      internal class ResultSelectorRewriter&lt;TOuter, TInner, TResult&gt; : ExpressionVisitor
      {
          private Expression&lt;Func&lt;TOuter, TInner, TResult&gt;&gt; resultSelector;
          public Expression&lt;Func&lt;KeyValuePairHolder&lt;TOuter, IEnumerable&lt;TInner&gt;&gt;, TInner, TResult&gt;&gt; CombinedExpression { get; private set; }
  
          private ParameterExpression OldTOuterParamExpression;
          private ParameterExpression OldTInnerParamExpression;
          private ParameterExpression NewTOuterParamExpression;
          private ParameterExpression NewTInnerParamExpression;
  
          public ResultSelectorRewriter(Expression&lt;Func&lt;TOuter, TInner, TResult&gt;&gt; resultSelector)
          {
              this.resultSelector = resultSelector;
              this.OldTOuterParamExpression = resultSelector.Parameters[0];
              this.OldTInnerParamExpression = resultSelector.Parameters[1];
  
              this.NewTOuterParamExpression = Expression.Parameter(typeof(KeyValuePairHolder&lt;TOuter, IEnumerable&lt;TInner&gt;&gt;));
              this.NewTInnerParamExpression = Expression.Parameter(typeof(TInner));
  
              var newBody = this.Visit(this.resultSelector.Body);
              var combinedExpression = Expression.Lambda(newBody, new ParameterExpression[] { this.NewTOuterParamExpression, this.NewTInnerParamExpression });
              this.CombinedExpression = (Expression&lt;Func&lt;KeyValuePairHolder&lt;TOuter, IEnumerable&lt;TInner&gt;&gt;, TInner, TResult&gt;&gt;)combinedExpression;
          }
  
          protected override Expression VisitParameter(ParameterExpression node)
          {
              if (node == this.OldTInnerParamExpression)
                  return this.NewTInnerParamExpression;
              else if (node == this.OldTOuterParamExpression)
                  return Expression.PropertyOrField(this.NewTOuterParamExpression, "Item1");
              else
                  throw new InvalidOperationException("What is this sorcery?", new InvalidOperationException("Did not expect a parameter: " + node));
          }
      }
  
      public static class JoinExtensions
      { 
          internal static readonly System.Reflection.MethodInfo
              Enumerable_DefaultIfEmpty = typeof(Enumerable).GetMethods()
                  .First(x =&gt; x.Name == "DefaultIfEmpty" &amp;&amp; x.GetParameters().Length == 1);
          internal static readonly System.Reflection.MethodInfo
              Queryable_SelectMany = typeof(Queryable).GetMethods()
                  .Where(x =&gt; x.Name == "SelectMany" &amp;&amp; x.GetParameters().Length == 3)
                  .OrderBy(x =&gt; x.ToString().Length).First();
          internal static readonly System.Reflection.MethodInfo
              Queryable_Where = typeof(Queryable).GetMethods()
                  .First(x =&gt; x.Name == "Where" &amp;&amp; x.GetParameters().Length == 2);
          internal static readonly System.Reflection.MethodInfo
              Queryable_GroupJoin = typeof(Queryable).GetMethods()
                  .First(x =&gt; x.Name == "GroupJoin" &amp;&amp; x.GetParameters().Length == 5);
  
          public static IQueryable&lt;TResult&gt; LeftJoin&lt;TOuter, TInner, TKey, TResult&gt;(
                     this IQueryable&lt;TOuter&gt; outer,
                     IQueryable&lt;TInner&gt; inner,
                     Expression&lt;Func&lt;TOuter, TKey&gt;&gt; outerKeySelector,
                     Expression&lt;Func&lt;TInner, TKey&gt;&gt; innerKeySelector,
                     Expression&lt;Func&lt;TOuter, TInner, TResult&gt;&gt; resultSelector)
          {
  
              var keyValuePairHolderWithGroup = typeof(KeyValuePairHolder&lt;,&gt;)
                  .MakeGenericType(
                      typeof(TOuter),
                      typeof(IEnumerable&lt;&gt;).MakeGenericType(typeof(TInner))
                  );
              var paramOuter = Expression.Parameter(typeof(TOuter));
              var paramInner = Expression.Parameter(typeof(IEnumerable&lt;TInner&gt;));
  
              var resultSel = Expression
                  .Lambda(
                      Expression.MemberInit(
                          Expression.New(keyValuePairHolderWithGroup),
                          Expression.Bind(
                              keyValuePairHolderWithGroup.GetMember("Item1").Single(),
                              paramOuter
                              ),
                          Expression.Bind(
                              keyValuePairHolderWithGroup.GetMember("Item2").Single(),
                              paramInner
                              )
                          ),
                      paramOuter,
                      paramInner
                  );
              var groupJoin = Queryable_GroupJoin
                  .MakeGenericMethod(
                      typeof(TOuter),
                      typeof(TInner),
                      typeof(TKey),
                      keyValuePairHolderWithGroup
                  )
                  .Invoke(
                      "ThisArgumentIsIgnoredForStaticMethods",
                      new object[]{
                          outer,
                          inner,
                          outerKeySelector,
                          innerKeySelector,
                          resultSel
                      }
                  );
  
  
              var paramGroup = Expression.Parameter(keyValuePairHolderWithGroup);
              Expression collectionSelector = Expression.Lambda(
                              Expression.Call(
                                      null,
                                      Enumerable_DefaultIfEmpty.MakeGenericMethod(typeof(TInner)),
                                      Expression.MakeMemberAccess(paramGroup, keyValuePairHolderWithGroup.GetProperty("Item2")))
                              ,
                              paramGroup
                          );
  
              Expression newResultSelector =
                  new ResultSelectorRewriter&lt;TOuter, TInner, TResult&gt;(resultSelector)
                      .CombinedExpression;
  
  
              var selectMany1Result = Queryable_SelectMany
                  .MakeGenericMethod(
                      keyValuePairHolderWithGroup,
                      typeof(TInner),
                      typeof(TResult)
                  )
                  .Invoke(
                      "ThisArgumentIsIgnoredForStaticMethods",
                      new object[]
                      {
                          groupJoin,
                          collectionSelector,
                          newResultSelector
                      }
                  );
              return (IQueryable&lt;TResult&gt;)selectMany1Result;
          }
      }
  }
  </code></pre>
  
  <p>When I run the above with a data context by EF Core 2.0 I get the following exception thrown at runtime:</p>
  
  <pre><code>System.ArgumentNullException occurred
    HResult = 0x80004003
    Message = Value cannot be null.
      Source =&lt; Cannot evaluate the exception source&gt;
        StackTrace:
     at Remotion.Utilities.ArgumentUtility.CheckNotNull[T](String argumentName, T actualValue)
     at Remotion.Utilities.ArgumentUtility.CheckNotNullOrEmpty(String argumentName, String actualValue)
     at Remotion.Linq.Clauses.GroupJoinClause..ctor(String itemName, Type itemType, JoinClause joinClause)
     at Remotion.Linq.Parsing.Structure.IntermediateModel.GroupJoinExpressionNode.ApplyNodeSpecificSemantics(QueryModel queryModel, ClauseGenerationContext clauseGenerationContext)
     at Remotion.Linq.Parsing.Structure.IntermediateModel.MethodCallExpressionNodeBase.Apply(QueryModel queryModel, ClauseGenerationContext clauseGenerationContext)
     at Remotion.Linq.Parsing.Structure.QueryParser.ApplyAllNodes(IExpressionNode node, ClauseGenerationContext clauseGenerationContext)
     at Remotion.Linq.Parsing.Structure.QueryParser.ApplyAllNodes(IExpressionNode node, ClauseGenerationContext clauseGenerationContext)
     at Remotion.Linq.Parsing.Structure.QueryParser.GetParsedQuery(Expression expressionTreeRoot)
     at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, INodeTypeProvider nodeTypeProvider, IDatabase database, IDiagnosticsLogger`1 logger, Type contextType)
     at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.&lt;&gt; c__DisplayClass15_0`1.&lt; Execute &gt; b__0()
     at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
     at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
     at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
     at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
     at Remotion.Linq.QueryableBase`1.GetEnumerator()
     at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
     at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
     at TestWithEFCore2.Program.Main() in C: \Users\hewasud\Git\TestLeftJoinExtensionWithEF6\TestWithEF6\TestWithEFCore2\Program.cs:line 27
  </code></pre>
  
  <p>My questions:</p>
  
  <ol>
  <li>Is this a bug in EF Core 2.0 or am I doing something wrong here?</li>
  <li>Is there a better way to produce an extension method which combines the GroupJoin and SelectMany methods to perform a LeftJoin when using EF Core 2.0 assuming that this code must be functional within a .Net Standard class library allowing portability?</li>
  </ol>
  
  <p>Thank you.</p>