【问题标题】:Entity Framework Core - Use Extension Methods Inside QueryableEntity Framework Core - 在 Queryable 中使用扩展方法
【发布时间】:2019-06-27 22:58:11
【问题描述】:

我有以下查询:

db.Users.AsQueryable()
    .Where(u => u.Id = userResolver.LoggedUserId() && u.Packages.Where(p => 
            p.StatusId == (int)PackageStatus.InProgress ||
            p.StatusId == (int)PackageStatus.Delivered ||
            p.StatusId == (int)PackageStatus.Shipped ||
            p.StatusId == (int)PackageStatus.Waiting) 
        .Sum(p => p.Price) > u.MaxCredit)
    .ToList()

我想要实现的是将所有包状态检查分组到一个扩展方法。类似的东西:

db.Users.AsQueryable()
        .Where(u => u.Id = userResolver.LoggedUserId() &&
             u.Packages.Where(p => p.IsShippedOrInProgress())
            .Sum(p => p.Price) > u.MaxCredit)
        .ToList()


 //This is the extension method
 public static bool IsShippedOrInProgress(this Package p) {
    return p.StatusId == (int)PackageStatus.InProgress ||
           p.StatusId == (int)PackageStatus.Delivered ||
           p.StatusId == (int)PackageStatus.Shipped ||
           p.StatusId == (int)PackageStatus.Waiting)
 }

当我查看第一个示例中生成的 sql 查询时,一切似乎都正常,但是当我使用第二种方法时,检查状态的查询部分不存在。

【问题讨论】:

  • 还有更多代码没有显示吗?
  • 是的。其实这只是一个例子。我不能发布真正的代码。我很乐意为您提供您需要的任何东西:-)
  • @TalHumy 更清楚地解释您对这些查询的期望?
  • 自定义方法由于显而易见的原因无法转换为 SQL。一种可能的解决方案是使用 3rd 方库,例如 NeinLinq.EntityFrameworkCore“NeinLinq 扩展了 LINQ 提供程序,例如 Entity Framework,以启用重用函数、重写查询以及使用可翻译的谓词和选择器构建动态查询。”
  • 您可能需要探索 EF 如何将您的 C# 代码转换为 SQL 查询。您可以通过让扩展方法返回 Expression<Func<Package, Boolean>> 来解决这个问题

标签: c# entity-framework entity-framework-core


【解决方案1】:

试试这个方法,它将创建ExpressionPackage 作为输入,bool 作为输出:

public static System.Linq.Expressions.Expression<Func<Package, bool>> IsShippedOrInProgress()
{
    return p => p.StatusId == ( int )PackageStatus.InProgress ||
           p.StatusId == ( int )PackageStatus.Delivered ||
           p.StatusId == ( int )PackageStatus.Shipped ||
           p.StatusId == ( int )PackageStatus.Waiting);
}

这样称呼它:

private void SomeMethod()
{
    db.Users.AsQueryable()
    .Where(u => u.Id = userResolver.LoggedUserId() &&
         u.Packages.Where(IsShippedOrInProgress())
        .Sum(p => p.Price) > u.MaxCredit)
    .ToList()
}

【讨论】:

  • 所以我尝试执行以下操作: db.Users.AsQueryable() .Where(u => u.Id = userResolver.LoggedUserId() && p.Packages.AsQueryable().Where(IsShippedOrInProgress ()) 但我收到以下错误:无法解析表达式“u.Package.AsQueryable().Where(IsShippedOrInProgress(p.OwnerId))”:给定的参数与预期的参数不匹配:“系统”类型的对象。 Linq.Expressions.MethodCallExpression1' 无法转换为类型 'System.Linq.Expressions.LambdaExpression'。
  • 更简单的选项 +1
  • @TalHumy 不知道为什么IsShippedOrInProgress 方法接收p.OwnerId 参数。它应该是Package 类型。这个u.Packages.Where(p =&gt; IsShippedOrInProgress()) 应该与实例化方法IsShippedOrInProgress 一起使用。
  • @AnkushMadankar 创建表达式很简单。问题是如何在另一个表达式树中使用它。如果您仔细考虑,您会发现 Where(p =&gt; IsShippedOrInProgress()) 不是有效的 C# 代码。从IQueryable 的引入开始,这个问题就已经很古老了。没有表达式树操作无法解决。由 LINQKit、NeinLINQ、AutoMapper 和其他软件包解决。
  • @TalHumy 请查看这篇文章以了解有关在 Lamba 中组合多个表达式的详细信息:stackoverflow.com/questions/457316/…
【解决方案2】:

我试着用记事本写这个。所以可能会有错误。在 cmets 中告诉我

public static Expression<Func<Package,bool>> IsShippedOrInProgress() {

    // Compose the expression tree that represents the parameter to the predicate.  
    ParameterExpression p = Expression.Parameter(typeof(Package), "p");  

    // Compose left side of the expression i.e `p.StatusId`
    Expression left = Expression.Call(p, typeof(Package).GetProperty("StatusId"));  

    // Compose right side of the expression i.e `(int)PackageStatus.InProgress` etc.
    Expression exprInProgress = Expression.Constant((int)PackageStatus.InProgress);  
    Expression exprDelivered = Expression.Constant((int)PackageStatus.Delivered);  
    Expression exprShipped = Expression.Constant((int)PackageStatus.Shipped);  
    Expression exprWaiting = Expression.Constant((int)PackageStatus.Waiting);  

    // Compose left equals right side
    Expression e1 = Expression.Equal(left, exprInProgress);  
    Expression e2 = Expression.Equal(left, exprDelivered);  
    Expression e3 = Expression.Equal(left, exprShipped);  
    Expression e4 = Expression.Equal(left, exprWaiting);  

    //Compose `p.StatusId == (int)PackageStatus.InProgress ||
    //       p.StatusId == (int)PackageStatus.Delivered ||
    //       p.StatusId == (int)PackageStatus.Shipped ||
    //       p.StatusId == (int)PackageStatus.Waiting`
    Expression orConditions = Expressions.OrElse(Expression.OrElse(Expression.OrElse(e1,e2),e3),e4);

    //Compose `p => 
    //        p.StatusId == (int)PackageStatus.InProgress ||
    //        p.StatusId == (int)PackageStatus.Delivered ||
    //        p.StatusId == (int)PackageStatus.Shipped ||
    //        p.StatusId == (int)PackageStatus.Waiting`
    return Expression.Lambda<Func<Package, bool>>(orConditions, new ParameterExpression[] { p })); 

}

更新

The C# compiler can generate expression trees from expression lambdas (or single-line lambdas)。请检查@Ankush 答案

【讨论】:

  • 嗨拉梅什,感谢您的帮助。我希望有一个更简单的解决方案,但这个可行
  • @TalHumy - Ankush 的答案会更简单。将方法内容替换为他给出的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-16
  • 2020-06-07
  • 2017-12-27
  • 1970-01-01
相关资源
最近更新 更多