Linq-to-SQL, pass Expression<Func<T, T>> to select method in query syntax

4.3k Views Asked by At

Suppose, we have next code:

public class Dto
{
  public int Id;
  public string Name;
}    

...

using (var db = new NorthwindDataContext())
{
  var q = from boss in db.Employees
          from grunt in db.Employees.Where(p => p.ReportsTo == boss.EmployeeID).DefaultIfEmpty()
          select new Dto { Id = boss.EmployeeID, Name = grunt.FirstName };
}

I want to extract selector as Expression and store it in another place. In method syntax it will look like this:

Expression<Func<Employee, Employee, Dto>> selector = (boss, grunt) => new Dto
{
  Id = boss.EmployeeID, Name = grunt.FirstName
};

using (var db = new NorthwindDataContext())
{
  var q = db.Employees.SelectMany(boss => db.Employees.Where(p => p.ReportsTo == boss.EmployeeID).DefaultIfEmpty(), selector);
}

Is it possible to convert this LinqToSql method chain to query syntax keeping Expression variable in-place?

UPD:

To clarify my question, I'm using DefaultIfEmpty for left join, it's a short form of equal query:

using (var db = new NorthwindDataContext())
{
  var q = from boss in db.Employees
          join stub in db.Employees on boss.EmployeeID equals stub.ReportsTo into stubi
          from grunt in stubi.DefaultIfEmpty()
          select new Dto { Id = boss.EmployeeID, Name = grunt.FirstName };
}

which works normally, because it compiles using inline Expression. It assigns null to Name field when there is no corresponding grunt. But if rewrite this query with call to external mapper method, it will be compiled to method call, which will get nullable grunt argument and will lead to NullReferenceException:

public static Dto GetDto(Employee boss, Employee grunt)
{
  return new Dto
    {
      Id = boss.EmployeeID,
      Name = grunt.FirstName
    };
}

using (var db = new NorthwindDataContext())
{
  var q = from boss in db.Employees
          join stub in db.Employees on boss.EmployeeID equals stub.ReportsTo into stubi
          from grunt in stubi.DefaultIfEmpty()
          select GetDto(boss, grunt);
}

Sure, I can add null-check into mapper method, but what I'm trying to achieve in my DAL is extract selectors into mapper classes and possibly omit null-checks there.

2

There are 2 best solutions below

5
On

I'm not sure why you need the Expression - just use the Func. This should work:

Func<Employee, Employee, Dto> selector = (boss, grunt) => new Dto 
{ 
Id = boss.EmployeeID, Name = grunt.FirstName 
}; 

using (var db = new NorthwindDataContext()) 
{ 
var q = from boss in db.Employees 
        from grunt in db.Employees.Where(p => p.ReportsTo == boss.EmployeeID).DefaultIfEmpty() 
        select selector(boss, grunt)
} 
0
On

You cannot use query syntax always, there are cases where you can express computations only with method chains. In this specific case the query syntax would introduce a lambda behind the scenes if the predicate was inline, but you are putting it into a variable so you have no way to specify how that variable should be used, as you would with a lambda, supported by query syntax.