【问题标题】:Dynamically Modifying Where Condition in LINQ Query在 LINQ 查询中动态修改 Where 条件
【发布时间】:2013-01-30 07:26:41
【问题描述】:

以下实体框架查询运行没有错误。

Predicate<Program> filterProgram;
if (programId.HasValue)
    filterProgram = (p => p.Id == programId && !p.IsDeleted);
else
    filterProgram = (p => !p.IsDeleted);

var analytics = (from a in repository.Query<Analytic>()
                 where (a.Marker == "Open" || a.Marker == "LastTouch") &&
                 a.EntityType == "Proposal" &&
                 a.Site == "C"
                 join p in repository.Query<Program>()
                 on a.EntityId equals p.Id
                 //where filterProgram(p)
                 group a
                 by new { a.LoginSessionId, a.EntityId, p.Id, p.Name } into g
                 let f = g.OrderBy(x => x.TimestampUtc).FirstOrDefault(x => x.Marker == "Open")
                 where f != null
                 let t = g.FirstOrDefault(x => x.Marker == "LastTouch" && x.TimestampUtc > f.TimestampUtc)
                 select new
                 {
                     ProgramId = g.Key.Id,
                     Program = g.Key.Name,
                     ProposalId = g.Key.EntityId,
                     FirstOpen = f,
                     LastTouch = (t ?? f).TimestampUtc
                 }).ToList();

但是,如果我取消注释 where filterProgram(p) 行,我会收到运行时错误:

LINQ to Entities 不支持 LINQ 表达式节点类型“Invoke”。

我期待 LINQ 能够将我的谓词合并到查询中并将其转换为 SQL。为什么会出现这个错误,有没有办法以这种方式动态修改 where 谓词?

【问题讨论】:

  • 我不确定这是否可行,但您可以尝试将 filterProgram 定义为 Expression&lt;Predicate&lt;Program&gt;&gt; 吗?
  • 嗯,这给了我一个关于谓词被称为“'filterProgram' 是一个'变量'但被用作'方法'”的编译错误。

标签: c# linq entity-framework predicate


【解决方案1】:

问题是因为实体框架需要能够将您的 LINQ 查询转换为 SQL。您的 LINQ 查询被编译成称为表达式树的数据结构,然后将其传递给实体框架以转换为 SQL。

你有两个选择:

  1. 用更基本的 C# 表达式替换您对 filterProgram 的调用。根据filterProgram 的复杂性,这可能是不可能的。
  2. 删除您对filterProgram 的调用,并将此查询转换为IEnumerable&lt;T&gt;,可能通过调用.ToList()。然后,您可以使用filterProgram 进一步过滤此查询的结果

2 示例

   var query = /* Your Query With filterProgram commented out */
   var resultsFromSql = query.ToList();
   var fullyFiltered = resultsFromSql.Select(filterProgram);

【讨论】:

  • 我认为第二个选项不是一个好的选项,因为它会从数据库中提取所有未过滤的行。但也许我可以修改查询以执行所有filterProgram 版本所需的测试。
  • @JonathanWood 这实际上取决于没有filterProgram 的查询返回了多少行。正如您所说,可以通过将filterProgram 的一些功能移动到查询中并在获取结果后执行其余部分来使用这两个选项。
【解决方案2】:

filterProgram 的类型更改为Expression&lt;Func&lt;Program, bool&gt;&gt;,然后它应该可以在LINQ Where 子句中使用。

但有一个警告:我设法让它在方法链语法中工作,但不是在查询语法中。

例如,这是有效的:

dataContext.Programs.Where (filterProgram)

但事实并非如此:

from p in dataContext.Programs
where filterprogram 

(编译器抱怨它无法解析方法Where,这在IEnumerableIQueryable 上都可用。)

在您的情况下,替换该行可能是可以接受的

join p in repository.Query<Program>()

join p in repository.Query<Program>().Where(filterProgram)

【讨论】:

    【解决方案3】:

    在 Linq to Entities 中,如果不将查询转换为 IEnumerable,则无法调用外部方法。因此,由于filterProgram 是一个方法,您不能在查询内部调用。

    如果可能,您可以在调用FilterProgram 之前先调用ToList,它会起作用。 另一种选择可能是将filterProgram 的逻辑插入到查询中。

    【讨论】:

    • 在过滤器之前调用ToList() 会非常低效,因为它会从数据库中获取所有过滤后的项目。将逻辑插入到查询中是我想做的,但是如何动态的呢?
    • @JonathanWood 你是绝对正确的,但我在这里看不到任何选择。你可以参考stackoverflow.com/questions/2772939/call-method-from-linq-query
    【解决方案4】:

    问题是 linq2entities 试图将表达式树转换为 SQL 您的表达式树在委托类型 (filterProgram) 上调用了方法 Invoke 但是没有什么能阻止你内联该谓词

    var id= programId.HasValue ? programId.GetValueOrDefault() : -1;
    var analytics = (from a in repository.Query<Analytic>()
                     where (a.Marker == "Open" || a.Marker == "LastTouch") &&
                     a.EntityType == "Proposal" &&
                     a.Site == "C"
                     join p in repository.Query<Program>()
                     on a.EntityId equals p.Id
                     where !p.IsDeleted && (!hasValue || p.Id == id)
                     ....
    

    这假定 -1 是无效的 programId

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 2020-05-09
      • 2018-01-12
      • 1970-01-01
      相关资源
      最近更新 更多