在LINQ查询中使用Expression<Func<>>

7
我希望定义一个名为CompareProductItemVendorIdsFunc<ProductItemVendor, bool>过滤表达式,它可以在我的应用程序中使用,在Entity Framework/LINQ查询中使用较多。
我了解,为了能够在LINQ查询中使用此过滤器,必须将其声明为Expression<Func<>>而不是Func<>。我理解其原因,并且很容易做到这一点。
但我在查询中使用该表达式时遇到以下问题。
首先,类似于:
ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds)
注意:ProductItem是一个数据库实体,它的ProductItemVendors属性是一个导航集合。

会产生如下错误:

“实例参数:无法将类型为 'System.Collections.Generic.ICollection' 的对象转换为类型 'System.Linq.IQueryable'

其次,下面这段代码也会产生问题:

var results = from v in Repository.Query<ProductItemVendor>()
              where CompareProductItemVendorIds(v)
              select v;

产生错误:

'CompareProductItemVendorIds' 是一个 '变量',但是像一个 '方法' 一样使用了

我有一个漂亮的新 Expression<Func<>>。我如何在我的LINQ查询中使用它?


表达式是对一个函数的描述,而不是函数本身。为什么不能直接使用Func<> - Cameron
@Cameron:因为我会收到错误信息 The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. 请参考 https://dev59.com/IITba4cB1Zd3GeqP9a6m。 - Jonathan Wood
2
@Seb:在这种情况下,我的理解是它返回查询中的所有项目,然后在内存中查找匹配项。这是一种非常低效的方法。我正在尝试避免这种情况。 - Jonathan Wood
1
"ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds)" 只是一个表达式,你能给出完整的语句吗?(在 EF 中上下文非常重要) - flindeberg
1
@JonathanWood,相信我,它确实有效。 - flindeberg
显示剩余23条评论
3个回答

5

ProductItem 已经是一个 Entity,因此您无法使用 Expression,您需要使用 Compile() 从您的 Expression<Func<>> 获取 Func<>,因为 ProductItemVendors 不再是一个 IQueryable

ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds.Compile())

您需要在ProductItemVendorsContext上使用您的表达式,如下所示:
var item = Context.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds);

您不能在查询语法中使用表达式,需要使用方法语法

var results = from v in Repository.Query<ProductItemVendor>()
                                  .Where(CompareProductItemVendorIds)
              select v;

1
返回 Func<T> 会导致查询使用 Linq-to-Objects 而不是 Linq-to-Entities 执行,这在此情况下是不希望的。 - haim770
我认为这是一个非常糟糕的方法。我正在使用Entity Framework,表达式需要被编译成SQL。这个建议会编译成机器代码,从数据库中加载所有项,然后在内存中过滤这些数据。 - Jonathan Wood
@Aducci:你可以将ProductItem加载到内存中,而无需加载每个相关的ProductItemVendor。每当加载一个实体时,加载所有相关实体并不是EF所做的。它会在这里使用延迟加载 - Jonathan Wood
1
根据您的设置方式而定。我使用Include加载我想要加载的导航属性。看起来您正在使用延迟加载,这意味着您第一次使用ProductItemVendors导航属性时会进行加载。 - Aducci
这是重要提示:_您不能在查询语法中使用表达式,您需要使用方法语法_。我建议在该行以下删除所有内容,因为它会让读者感到困惑。 - Chris Schaller
显示剩余4条评论

1
我不认为默认情况下支持这种组合表达式。
你可以尝试使用LINQKit
下面是来自 LINQKit 页面的示例,一个 Expression<Func<>> 嵌套在另一个中:

Call Invoke to call the inner expression Call Expand on the final result. For example:

Expression<Func<Purchase,bool>> criteria1 = p => p.Price > 1000;
Expression<Func<Purchase,bool>> criteria2 = p => criteria1.Invoke (p)
                                                 || p.Description.Contains ("a");

Console.WriteLine (criteria2.Expand().ToString()); 

(Invoke and Expand are extension methods in LINQKit.) Here's the output:

p => ((p.Price > 1000) || p.Description.Contains("a"))

Notice that we have a nice clean expression: the call to Invoke has been stripped away.


1
第一个案例;
ProductItemVendor productItemVendor = ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds);

产生错误:

`Instance argument: cannot convert from 'System.Collections.Generic.ICollection' to 'System.Linq.IQueryable'

因为它是一个ICollection,所以实体已经被加载。鉴于dbContextobjectContext的惰性加载与显式加载实现有些不同,我假设您正在使用显式加载。如果您将加载更改为惰性,则ProductItemVendors的类型将为IQueryable,您正在尝试的内容将成功。
在第二种情况下,表达式必须可编译为SQL,否则会出现许多奇怪的错误,可能这就是这里的情况。
鉴于问题中的信息,很难给予更明确的帮助,我无法轻松地重新创建它。如果您可以创建一个MWE解决方案并将其上传到某个地方,我可以查看一下,但恐怕我在这里不能提供更多帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接