简单的 LINQ 到实体翻译

Posted

技术标签:

【中文标题】简单的 LINQ 到实体翻译【英文标题】:Simple LINQ to Entities translation 【发布时间】:2019-08-15 16:03:15 【问题描述】:

在有人将标记视为重复之前,我已经查看了每个人都在做的事情比我尝试的要复杂一些。

所以我正在开发一个数据库,其中有很多数据要检查,而 LINQ 的 Any() 扩展转换为 SQL 不如 SQL 的 Count(1) > 0 快,所以我在写的任何地方:

var someBool = Ctx.SomeEntities.Count(x => x.RelatedEntity.Count(y => y.SomeProperty == SomeValue) > 0) > 0;

在伪中:我的任何实体是否与具有值为 SomeValue 的属性的某个其他实体有关系。

这很好用,而且运行速度很快。但是,它并不完全可读(我有很多,在某些情况下比嵌入更多)所以我想做的就是将其替换为:

var someBool = Ctx.SomeEntities.AnyX(x => x.RelatedEntity.AnyX(y => y.SomeProperty == SomeValue));

与:

public static bool AnyX<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) => source.Count(predicate) > 0;

所以你看我没有做任何 LINQ 无法转换为 SQL 的事情,我没有做任何 LINQ 尚未转换为 SQL 的事情,而只是通过创建一个额外的扩展我得到:

LINQ to Entities 无法识别 Boolean AnyX 等方法...

必须有某种方式来编写我的扩展程序或某种方式告诉 LINQ 只是为了看看代码,你就会发现你可以做到。

【问题讨论】:

你为什么使用 AnyX 而不是 Any? 我看到了两个大问题,首先 EF 处理 Expressions,而不是代表;传递Expression&lt;Func&lt;TSource, bool&gt;&gt; 而不是Func&lt;TSource, bool&gt;,它需要扩展IQueryable&lt;&gt;,而不是IEnumerable @Tod 您复制了 Any 的 IEnumerable 版本的代码。 IQueryable 版本不同。 IEnumerable版本,IQueryable版本 在尝试以这种方式“增强”实体框架时,了解扩展 IEnumerableIQueryable 之间的区别至关重要。如果您在IEnumerable 上操作,它将在本地计算机上运行,​​不会被翻译成 SQL。 见***.com/a/36736907/1625737 【参考方案1】:

不是针对您的具体问题的答案,但我建议您重新考虑如何处理查询。

让我们使用一些更容易理解的描述性名称:是否有任何家庭的居民的名字是“Bobby”?

// your way
Ctx.Households.Count( hh => hh.Residents.Count( r => r.FirstName == "Bobby" ) > 0 ) > 0

糟糕,它倒退了。从居民开始:

Ctx.Residents.Count( r => 
    r.FirstName == "Bobby"
    && r.Household != null ) // if needed
    > 0;

现在,生成的 SQL 会与下面的明显不同吗?

Ctx.Residents.Any( r => r.FirstName == "Bobby" && r.Household != null)

编辑:

这是一个与你的结论相反的真实 MCVE:

/*
create table TestDatum
(
    TestValue nchar(36) not null
)
*/

/*
set nocount on
declare @count int
declare @start datetime
declare @end datetime
set @count = 0

set @start = GETDATE()

while @count < 14000000
begin
    insert TestDatum values( CONVERT(nchar(36), NEWID()) )

    set @count = @count + 1

    if (@count % 100000) = 0
    begin
        print convert(nvarchar, @count)
    end
end

set @end = GETDATE()

select CONVERT(nvarchar, DATEDIFF(ms, @start, @end))
*/

/*
-- "Any" test
declare @startdt datetime, @enddt datetime
set @startdt = GETDATE()
DECLARE @p0 NVarChar(1000) = '%abcdef%'

SELECT 
    (CASE 
        WHEN EXISTS(
            SELECT NULL AS [EMPTY]
            FROM TestDatum AS [t0]
            WHERE [t0].TestValue LIKE @p0
            ) THEN 1
        ELSE 0
     END) AS [value]
set @enddt = GETDATE()
select DATEDIFF(ms, @startdt, @enddt) -- ~7000ms
*/
/*
-- "Count" test
declare @startdt datetime, @enddt datetime
set @startdt = GETDATE()
-- Region Parameters
DECLARE @p0 NVarChar(1000) = '%abcdef%'
-- EndRegion
SELECT COUNT(*) AS [value]
FROM TestDatum AS [t0]
WHERE [t0].TestValue LIKE @p0
set @enddt = GETDATE()
select DATEDIFF(ms, @startdt, @enddt) -- > 48000ms
*/

【讨论】:

是 SQL 将 any 转换为:CASE WHEN EXISTS( SELECT NULL AS [EMPTY] FROM。Count 就是 COUNT @Tod - 好的,你是说计算所有记录比找到第一条记录时短路更快或更优化? 是的,这正是我要说的。我不知道为什么,我之前在 SO 上发布过,请检查问题中的 cmets 以获取链接。就两种方法的速度而言,一些数据可以进行比较。其他时候计数会快得多。 您能提供一个MCVE 重现此行为吗? 就像我说的,在问题的 cmets 中链接。 ***.com/questions/648795/…

以上是关于简单的 LINQ 到实体翻译的主要内容,如果未能解决你的问题,请参考以下文章

翻译C#表达式中的动态查询

LINQ to NHibernate - 如何检测失败的翻译

无法翻译 LINQ 表达式 DbSet<>.Any

无法翻译 LINQ 表达式。以可翻译的形式重写查询,或切换到客户端评估 EF Core 3.1

LINQ - '无法翻译表达式'与以前使用和验证的查询条件

LINQ 表达式“无法翻译”