Linq-to-SQL,将Expression<Func<T, T>>传递给查询语法中的select方法

4
假设我们有以下代码:
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 };
}

我希望能够提取选择器作为表达式并将其存储在其他位置。在方法语法中,它看起来像这样:

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);
}

能否将这个LinqToSql的方法链转换为查询语法,同时保持Expression变量不变?

更新:

为了澄清我的问题,我正在使用DefaultIfEmpty 进行左联接,它是相同查询的简写形式:

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 };
}

这段代码在使用内联表达式编译时可以正常工作。当没有对应的grunt时,它会将null赋值给Name字段。但是,如果将此查询重写为调用外部映射器方法,则会编译为方法调用,该方法将获取可为空的grunt参数,并导致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);
}

当然,我可以在映射器方法中添加空值检查,但我在DAL中试图实现的是将选择器提取到映射器类中,并可能在那里省略空值检查。
2个回答

0

我不确定你为什么需要 Expression - 直接使用 Func 就可以了。这应该可以工作:

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)
} 

1
正如您所看到的,这里使用了左连接。如果仅使用 Func,由于 grunt 可能等于 null,它将抛出 TargetInvocationException 和内部 NullReferenceException。但是,如果以方法链格式传递表达式给 Select 方法,则可以正常工作。 - dmitry.s
@Harm 你对此十分确定吗?编译器只是将双重“select”转换为对“SelectMany”的调用。无论如何,如果你担心空引用异常,为什么要调用DefaultIfEmpty呢? - Rawling
@Rawling,是的,我确定。我正在为左连接调用DefaultIfEmpty。我更新了问题以进行澄清。 - dmitry.s
@Harm Huh,DefaultIfEmpty 能够返回一个非空对象吗?即使对象的属性为空?这好奇怪啊。 - Rawling
@Rawling,对于Linq-to-objects,它会出现NRE错误,但对于使用Linq-to-SQL的内联选择谓词,则可以正常工作。我不知道确切的原因。这不是因为DefaultIfEmpty可以为空的右序列提供非空对象,而是与Linq-to-SQL选择器处理有关。 - dmitry.s

0

并不总是可以使用查询语法,有些情况下只能用方法链来表示计算。在这种特定情况下,如果谓词是内联的,则查询语法将在幕后引入lambda函数,但是您将其放入变量中,因此无法像lambda函数一样指定该变量的用途,而查询语法则支持lambda函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接