为什么这个Linq查询对于Count()返回0 ?

本文关键字:返回 Count Linq 查询 为什么 | 更新日期: 2023-09-27 18:01:28

这似乎不一致,但我可能只是错过了一些明显的东西。基本查询是:

var events = db.tbl_special_events.Where(x => x.TimeStamp >= startDate);
当我运行以下代码块时,出现了明显的不一致:
int c1 = 0;
foreach (var e in events)
{
    if (e.TimeStamp.DayOfWeek.ToString() == "Tuesday") c1++;
}
int c2 = events.Where(e => e.TimeStamp.DayOfWeek.ToString() == "Tuesday").Count();

运行之后,c1是1832,但c2是0。我错过了什么?

为什么这个Linq查询对于Count()返回0 ?

我重新创建了这个测试,发现它可能与DateTime函数直接相关。

生成的查询:

exec sp_executesql N'SELECT [t0].[User_ID], [t0].[Email], [t0].[Password], [t0].[BrandID], [t0].[CustomerID], [t0].[Created], [t0].[EndDate], [t0].[AccountStatusID], [t0].[Verified], [t0].[ResetPasswordFailCount], [t0].[ResetPasswordLocked]
FROM [dbo].[User] AS [t0]
WHERE ((CONVERT(NVarChar,CONVERT(Int,(DATEPART(dw, [t0].[Created]) + (@@DATEFIRST) + 6) % 7))) = @p0) AND ([t0].[BrandID] = @p1)',N'@p0 nvarchar(4000),@p1 int',@p0=N'Tuesday',@p1=3

注意@p0=N' tuesday '

请记住IQueryable和IEnumerable的不同之处在于IEnumerable代表一个实际的。net对象,IQueryable将你的表达式转换成一个实际的用于查询数据库的SQL语句。因此,你在表达式中提供的任何值实际上都被发送到数据库。

返回0个结果,因为没有匹配。原因是,SQL中的日期转换返回2而不是'Tuesday'。如果你在LINQ WHERE子句中将Tuesday替换为2,你可以对此进行测试,它实际上会工作。这将在枚举它之后工作,因为结果将被成功地映射到一个可用的。net对象中,其中DateTime。DayOfWeek转换为"Tuesday"将正常工作。

您的计数作用于IQueryable<Event>,而e是枚举实例。因此,有些操作可能无法工作,因为它们无法转换为SQL [Edit:或将转换为无意义的SQL]。

要确保Where子句有效,请在其前面添加AsEnumerable()。这会将IQueryable<Event>转换为IEnumerable<Event>,并告诉linq提供程序它应该在此时停止生成SQL。

因此,这个语句应该提供正确的结果:
int c2 = events.AsEnumerable()
    .Where(e => e.TimeStamp.DayOfWeek.ToString() == "Tuesday")
    .Count();

不能转换的实际代码在SQL中导致问题的是e.TimeStamp.DayOfWeek.ToString()

或者,你可以使用System.Data.Objects.SqlClient.SqlFunctions(这里的文档)类来提示linq提供程序在SQL中应该做什么。

编辑

正如@Servy指出的,这是一个Linq to SQL问题。然而,这个问题很常见,所以我留下了答案,不删除它。

再看一下OP,整个游戏可能还有另一个变量…延迟加载。

在foreach循环中,时间戳是延迟加载的。在计数查询中,提供程序尝试构造一个SQL查询,在此构造期间,它可能无法处理ToString(已知这是有问题的),并将e.TimeStamp.DayOfWeek.ToString()计算为与"Tuesday"不同的值。

AsEnumerable()强制提供者停止生成SQL,以便e.TimeStamp再次惰性加载。

唯一确切知道发生了什么的方法,是在DB上使用跟踪工具(例如SQL Server Profiler)来实际看到在服务器上执行的查询。

编辑2

基于@Sinaesthetic的回答,返回0的原因是查询试图将"4"与返回false的"Tuesday"进行比较,因此正确的结果是false

可以通过执行

来测试
select ((CONVERT(NVarChar,CONVERT(Int,(DATEPART(dw, getdate()) + (@@DATEFIRST) + 6) % 7))))

然而,这也表明由提供者来决定生成什么SQL。此外,提供者还可以决定它支持哪些语句,不支持哪些语句。

还表明使用AsEnumerable停止SQL生成过程会影响查询的语义求值