如何在 LINQ where 子句中传递 func 表达式?

How to pass func expression in LINQ where clause?

这是我要传入 where 子句的自定义过滤器 (Func)

Func<Project,bool> filter = f =>
{
    bool filteredContent = true;
    if (!CreatorId.Equals(0))
        filteredContent = f.CreatedBy.Equals(CreatorId);

    if (filteredContent && !VerticalMarketId.Equals(0))
        filteredContent = f.VerticalMarketsId.Equals(VerticalMarketId);

    if (filteredContent && !ProductCategoryId.Equals(0))
        filteredContent = f.ProductCategoriesId.Equals(ProductCategoryId);

    return filteredContent;

};

这是我的代码,我根据在过滤器表达式中创建的条件获取所有项目

 getProjects = await _context.Projects.Where(x => x.IsDeleted == false && filter.Invoke(x))// Here I'm getting the exception
                .Include(PC => PC.ProjectComments.Where(x => x.IsDeleted == false))
                .Include(SP => SP.SharedProjects)
                .AsNoTracking().ToListAsync();

Exception:The LINQ expression (DbSet......) could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.

谁能告诉我如何使用表达式过滤数据?

注意:我可以在应用过滤器之前执行 ToListAsync(),但它会从数据库中获取所有记录,然后在客户端进行过滤。但是我想在服务器端过滤数据。

如果您使用的 Linq To Objects 应该有效,但您正在使用 Linq To SQL,在这种情况下,您必须考虑如何将此函数转换为有效的 SQL 语句。问问自己:如何在 SQL 语句中传递此函数调用?取决于你对表达式的主体做了什么,你不能把它翻译成SQL,你有时必须更简单。

候选解

  1. 在您的项目中添加 PredicateBuilder class。它将为您提供轻松的逻辑运算符来处理表达式。

    http://www.albahari.com/nutshell/predicatebuilder.aspx

  2. 尝试定义一个表达式并将其作为参数传递给查询方法链的 Where 方法。示例(阅读评论):

// define a expression with default condition
Expression<Func<Project, bool>> filter = f => !f.IsDeleted;

// check conditions to add new filtes with `And` logical operator
if (!CreatorId.Equals(0))
    filter = filter.And(f => f.CreatedBy.Equals(CreatorId));
else if (!VerticalMarketId.Equals(0))
    filter =  filter.And(f => f.VerticalMarketsId.Equals(VerticalMarketId));
else if (!ProductCategoryId.Equals(0))
    filter =  filter.And(f => f.ProductCategoriesId.Equals(ProductCategoryId));

// apply the filter on the query and execute it
getProjects = await _context.Projects.Where(filter)
    .Include(PC => PC.ProjectComments.Where(x => !x.IsDeleted))
    .Include(SP => SP.SharedProjects)
    .AsNoTracking()
    .ToListAsync();

注意:我没有测试这段代码,它可能应该以某种方式修复!

关于 Linq To 的重要提示 SQL:

  • 逻辑运算符没问题,而且往往可以很好地翻译成 sql;
  • Where(x => x.Children.Any(j => j.Children.Any())),每个 Any 调用都会在查询范围内生成一个子查询,请谨慎使用,因为它可能会影响您的数据库性能。
  • 如果您只需要检查某个项目是否存在,请使用 queryable.Any(expression)
  • 如果你需要检查然后做某事,最好使用queryable.FirstOrDefault(expression)并在使用前检查结果是否为空。
  • 使用 .Take(int).Skip(int) 的分页。
  • 始终通过调用 .ToList().ToArray() 或这些方法的异步版本来具体化您的查询。避免在顶层传递可查询的(查询可以在你想要的范围之外执行)。

我通过创建一个简单的表达式来解决这个问题,如下所示:

private static Expression<Func<Project, bool>> ProjectFilterExpression(
    int creatorId, 
    int verticalMarketId, 
    int productCategoryId)
{
    Expression<Func<Project, bool>> projectFilterExpression = pfe => 
    !pfe.IsDeleted
    //CreatorId Filter
    && (creatorId.Equals(0) || pfe.CreatedBy.Equals(creatorId))
    //Vertical Market Filter
    && (verticalMarketId.Equals(0) || pfe.VerticalMarketsId.Equals(verticalMarketId))
    // Product Category Filter
    && (productCategoryId.Equals(0) || pfe.ProductCategoriesId.Equals(productCategoryId)); 
    return projectFilterExpression;
}

然后我在我的过滤器方法中调用这个静态方法。

var filter = ProjectFilterExpression(CreatorId, VerticalMarketId, ProductCategoryId);

最后我在我的 LINQ where 子句中应用了这个过滤器

getProjects = await _context.Projects.Where(filter).AsNoTracking().ToListAsync();

一切正常。