[英]LINQ to Entities using datetime differently
抱歉,这个称呼可能很差; 我有一个问题比其他任何事情都要出于好奇。
我已经测试了用两种不同方式编写的同一LINQ to Entities语句,一种使用Datetime.Now,一种使用日期变量:
var timeNow = DateTime.Now;
var pendingMailshots = db.MailshotHistoryDatas.Where(m =>
m.SendDate < timeNow).ToList();
var pendingMailshots = db.MailshotHistoryDatas.Where(m =>
m.SendDate < DateTime.Now).ToList();
我发现他们发现的数据存在差异,经过一番挖掘和剖析,我发现了这一点:
exec sp_executesql N'SELECT
[Extent1].[MailshotGuid] AS [MailshotGuid],
[Extent1].[MailshotLineId] AS [MailshotLineId],
[Extent1].[SendDate] AS [SendDate],
[Extent1].[MessageType] AS [MessageType],
[Extent1].[SendStatus] AS [SendStatus],
[Extent1].[Recipients] AS [Recipients],
[Extent1].[SendAttempts] AS [SendAttempts],
[Extent1].[DateSent] AS [DateSent]
FROM [dbo].[MailshotLineDatas] AS [Extent1]
INNER JOIN [dbo].[MailshotDatas] AS [Extent2] ON [Extent1].[MailshotGuid] = [Extent2].[MailshotGuid]
WHERE ([Extent1].[SendDate] < @p__linq__0),N'@p__linq__0 datetime2(7),@p__linq__0='2018-01-04 15:11:26.5618636'
SELECT
[Extent1].[MailshotGuid] AS [MailshotGuid],
[Extent1].[MailshotLineId] AS [MailshotLineId],
[Extent1].[SendDate] AS [SendDate],
[Extent1].[MessageType] AS [MessageType],
[Extent1].[SendStatus] AS [SendStatus],
[Extent1].[Recipients] AS [Recipients],
[Extent1].[SendAttempts] AS [SendAttempts],
[Extent1].[DateSent] AS [DateSent]
FROM [dbo].[MailshotLineDatas] AS [Extent1]
INNER JOIN [dbo].[MailshotDatas] AS [Extent2] ON [Extent1].[MailshotGuid] = [Extent2].[MailshotGuid]
WHERE([Extent2].[StartDate] < (SysDateTime())))
使用datetime变量将查询作为存储过程并使用Datetime运行,现在将代码转换为TSQL。
谁能解释为什么? 另外,您认为更好的做法是什么?
在此先感谢亚当
如果您在过滤器表达式中传递DateTime.Now
,则提供程序知道如何处理它,并将其替换为对SYSDATETIME
的调用,否则它别无选择,只能假设您要查找特定的日期。 这就是Expressions在C#中的工作方式( 请参阅此处 )
至于使用哪一个,那真的取决于您的用例。 调用SYSDATETIME
将使用服务器上的时间,而后者将使用进行调用的计算机上的时间。 在实践中,这些可能仍然相同,或者至少仅稍有不同(考虑到网络延迟,时间漂移等)。
您正在定义一个表达式,稍后将执行它。
第一个LINQ表达式包含一个DateTime变量,该变量用某个值(DateTime.Now)初始化。 但是,在执行表达式时,该变量将不再具有当前日期的值。 因此,在实际执行表达式时使用参数。
在后一种情况下,您可以指定一个查询,其中filter子句必须使用当前日期/时间。 由于推迟执行该表达式,因此引擎不知道何时实际执行该表达式,因此它使用特定于数据库的函数来获取当前日期和时间。
对于这种情况,没有“什么是更好的做法”这样的东西。 这完全取决于您的用例。 此行为仅特定于LINQ查询的延迟执行:您实质上是在构建一个仅在调用ToList()/ ToArray / etc后才执行的表达式。
当您将代码重新编写为类似以下内容时:
var timeNow = DateTime.Now;
var query = db.MailshotHistoryDatas.Where(m =>
m.SendDate < timeNow);
var pendingMailshots = query.ToList();
在上面的示例中,查询将仅在调用query.ToList();
的代码行处执行query.ToList();
由于可以构建表达式并在程序中稍后执行它,因此LINQ必须使用带有参数的查询,以确保将正确的日期和时间传递给该查询,从而确保已定义了变量。
好吧,在第一种情况下,它知道当前的系统时间,因为您传递了DateTime.Now,因此LINQ试图在SQL中寻找等效项,即SysDateTime()。
在第二种情况下,您要传递值,SQL中没有等效项,这意味着LINQ将必须使其成为SP并传递值。
我强烈建议使用
var pendingMailshots = db.MailshotHistoryDatas.Where(m =>
m.SendDate < DateTime.Now).ToList();
因为这样可以确保它与SQL执行时一样最新。 如果您不是真正在寻找系统精度,而是在寻找变量传递的值,请使用
var timeNow = DateTime.Now;
var pendingMailshots = db.MailshotHistoryDatas.Where(m =>
m.SendDate < timeNow).ToList();
发生这种情况的原因是如何解析表达式树以将您的LINQ语句转换为SQL。 在使用变量存储DateTime的示例中,遍历的表达式是ParameterExpression,通过向查询添加参数将其转换为SQL。 在直接在LINQ查询中使用DateTime.Now的示例中,Expression的类型为MemberExpression,而Entity Framework的Expression Tree遍历器具有一个特殊的情况,即表示DateTime.Now的MemberExpression会将其转换为SysDateTime()的SQL。 。
如果您的意图始终是在查询中使用当前数据库时间,则可以在LINQ表达式中直接使用DateTime.Now。 如果要使用查询中在Application Server上捕获的特定时间,我将使用该变量。
该MSDN博客对此进行了全面的解释。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.