【问题标题】:Linq-to-SQL, pass Expression<Func<T, T>> to select method in query syntaxLinq-to-SQL,通过 Expression<Func<T, T>> 在查询语法中选择方法
【发布时间】:2012-10-23 09:06:03
【问题描述】:

假设,我们有下一个代码:

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 变量就地的查询语法?

UPD:

为了澄清我的问题,我使用 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 分配给名称字段。但是如果通过调用外部映射器方法重写此查询,它将被编译为方法调用,这将获得可空的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 中试图实现的是将选择器提取到映射器类中,并可能在那里省略空检查。

【问题讨论】:

    标签: c# linq linq-to-sql linq-query-syntax


    【解决方案1】:

    我不确定您为什么需要 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)
    } 
    

    【讨论】:

    • 如你所见,这里是左连接。如果只使用Func,它将抛出带有内部NullReferenceException 的TargetInvocationException,因为grunt 可能等于null。但它与以方法链格式传递给 Select 方法的 Expression 一起正常工作。
    • @Harm 你确定吗?孪生select 只是被编译器转换为对SelectMany 的调用。无论如何,如果您担心 null 咕噜声,为什么要调用 DefaultIfEmpty
    • @Rawling,是的,我确定。我正在调用 DefaultIfEmpty 进行左连接。我更新了问题以进行澄清。
    • @Harm Huh,DefaultIfEmpty 是否给出了一个具有空字段而不是空对象的非空对象,然后呢?很奇怪。
    • @Rawling,它对于带有 NRE 的 Linq-to-objects 失败,但对于带有 Linq-to-SQL 的内联选择谓词可以正常工作。我不知道具体,为什么。这不是因为 DefaultIfEmpty 可以为空右序列提供非空对象,而是因为 Linq-to-SQL 选择器处理的东西。
    【解决方案2】:

    您不能总是使用查询语法,在某些情况下您只能使用方法链来表达计算。在这种特定情况下,如果谓词是内联的,则查询语法将在幕后引入 lambda,但是您将其放入变量中,因此您无法指定应如何使用该变量,就像使用 lambda 一样,支持通过查询语法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-11
      • 2021-10-15
      相关资源
      最近更新 更多