如何使用 System.Linq.Expressions.Expression 基于子项进行筛选

本文关键字:筛选 System 何使用 Linq Expressions Expression | 更新日期: 2023-09-27 18:32:43

我有一个过滤器,我可以在许多方法中使用:

Expression<Func<Child, bool>> filter = child => child.Status == 1;

(实际上比这更复杂)

我必须执行以下操作

return db.Parents.Where(parent => parent.Status == 1 &&
                                  parent.Child.Status == 1);

条件与上述过滤器中的条件相同。

我想在此方法中重用过滤器。但我不知道怎么做。我试过了

return db.Parents.Where(parent => parent.Status == 1 &&
                                  filter(parent.Child));

但表达式不能用作方法

如何使用 System.Linq.Expressions.Expression 基于子项进行筛选

如果你想组合表达式,并且仍然能够使用linq-to-sql,你可能想看看LinqKit。它会进入表达式内部,并在 sql 转换之前用它们的内容替换所有函数调用。

这样您就可以直接使用

return db.Parents
       .AsExpandable()
       .Where(parent => parent.Status == 1 && filter(parent.Child));

你可以试试这个:

var compiledFilter = filter.Compile();
foreach (var parent in db.Parents.Where(parent => parent.Status == 1))
    if (compiledFilter(parent.Child))
        yield return parent;

它要求您拉动所有父项,但与@HugoRune的解决方案不同,它不需要父项:子项的 1:1 关系。

由于涉及不同的类型,我认为这对您的情况没有用,但以防万一,下面是如何将Expression组合的示例:如何将 LINQ 表达式合并为一个?

编辑:我之前建议使用Compile(),但这不适用于LINQ-to-SQL。

好吧,如果父子之间存在 1:1 的关系(不太可能,但该示例似乎暗示了这一点)然后您可以这样做:

  return db.Parents
  .Where(parent => parent.Status == 1)
  .Select(parent => parent.Child)
  .Where(filter)
  .Select(child=> child.Parent);

否则会很难。

您可以使用动态 linq 来做到这一点,但这可能是矫枉过正。

您可以手动生成表达式树,但这也非常复杂。我自己没有尝试过。

作为最后的手段,你当然可以随时调用yourQuery.AsEnumerable(),这将导致linq-to-sql到目前为止将你的查询转换为sql,并在客户端执行其余的工作;然后你可以.compile()你的表达式。但是,你失去了linq-to-sql的性能优势(并且compile()本身非常慢;每当它被执行时,它都会调用JIT编译器):

  return db.Parents
  .Where(parent => parent.Status == 1)
  .AsEnumerable()
  .Where(parent  => filter.Compile().Invoke(parent.Child))

就我个人而言,我只定义两次表达式,一次用于孩子,一次用于 parent.child:

   Expression<Func<Child, bool>> filterChild = child => child.Status == 1;
   Expression<Func<Parent, bool>> filterParent = parent => parent.Child.Status == 1;
可能

不是最优雅的,但可能比其他解决方案更容易维护

想出这个,检查这是否适合你

public interface IStatus { public int Status { get; set; } }
public class Child : IStatus { }
public class Parent : IStatus
{public Child Child { get; set; }  }
Func<IStatus, bool> filter = (x) => x.Status == 1;
var list = Parents.Where(parent => filter(parent) && filter(parent.Child));

希望这有帮助!

你能把表达式作为一个函数来代替吗?

而不是:

Expression<Func<Child, bool>> filter = child => child.Status == 1;

以这种方式使用与泛型函数相同的表达式:

Func<Child, bool> filter = child => child.Status == 1;

然后,您将能够以与尝试使用表达式相同的方式使用该函数:

return db.Parents.Where(parent => parent.Status == 1 &&
                                  filter(parent.Child));

编辑:我误解了这个问题。这是一个糟糕的答案。6+ 年后,我仍然收到评论说这不起作用。从卫生的角度来看,我不确定是否最好删除答案,或者添加此编辑并让答案作为绝对不起作用的示例。我愿意接受这方面的建议。

不需要外部库或使用表达式树。 相反,请编写 lambda 函数以使用查询链接并利用 LINQ 的延迟执行。

而不是:

Expression<Func<Child, bool>> filter = child => child.Status == 1;

将其重写为:

Func<IQueryable<Parent>, IQueryable<Parent>> applyFilterOnParent = query => query.Where(parent => parent.Child.Status == 1);

Func<IQueryable<Child>, IQueryable<Child>> applyFilterOnChild = query => query.Where(child => child.Status == 1);

现在,而不是:

return db.Parents.Where(parent => parent.Status == 1 && filter(parent.Child));

你可以写:

var query = db.Parents.AsQueryable(); query = applyFilterOnParent(query); return query.Where(parent => parent.Status == 1);

您可以在其他 LINQ 查询中重复使用 applyFilter 函数。 当您希望将 lambda 函数与 LINQ-to-SQL 一起使用时,此技术非常有效,因为 LINQ 不会将 lambda 函数转换为 SQL。