Search code examples
c#linqexpression-trees

How to move parts of a LINQ query to an reusable LINQ expression tree


I want to make parts of a LINQ query reusable by using expression trees (i think that's how its called).

Here's a simplified version of my query:

var lQuery = 
  from m in ...
  join a in ... into ta
  from ra in ta.DefaultIfEmpty()
  select 
    new {
      ...
      Status = ra != null ? ... : ..., /* <- i want this to be reusable */
      ...
    };

As you can see, the value of Status is determined via ? : syntax which is translated by LINQ automatically (a expression tree i suppose, the SQL logs show a CASE WHEN query in the end).

How do i move that code to a separate function so that my application does not throw a not supported exception at runtime?

I tried adding a function

public static System.Linq.Expressions.Expression<System.Func<%type of ra%, %status type%>>
  QueryStatusExpression()
{
  return ra => ra != null ? ... : ...;
}

then use it like

Status = QueryStatusExpression().Invoke(ra)

but it throws the not supported exception.

I'm out of ideas right now. Any help is appreciated.


Solution

  • The issue can be solved via combined expressions of the LINQKit project.

    Here's how to do it:

    1. Add the LinqKit reference to your project (e.g. via NuGet package manager).

    2. Add the following line on top of your .cs file to make the extension methods of LINQKit available

      using LinqKit; // for System.Linq.Expressions.Expression<>.Invoke()
      
    3. Define the expression as a static field

      public static System.Linq.Expressions.Expression<
        System.Func<%type of ra% ra, %status type%>>
          GetStatusExpression = ra != null ? ... : ...;
      
    4. Invoke the expression in the query (be sure to add .AsExpandable() to the first table as described in the LINQKit docs)

      var lQuery = 
        from m in %first table%.AsExpandable() ...
        join a in ... into ta
        from ra in ta.DefaultIfEmpty()
        select 
          new {
            ...
            Status = GetStatusExpression.Invoke(ra),
            ...
          };
      

    If you want to use the expression in "normal" code then you need to compile it first like

     public static System.Func<%type of ra% ra, %status type%>
        GetStatusExpressionCompiled = GetStatusExpression.Compile();
    
     ...
    
     if (GetStatusExpressionCompiled(ra) == ...)
     {
       ...
    

    Big thanks goes to svick for pointing me in the right direction with it's comment.