使用日期时间的LINQ to Entities不同

时间:2018-01-04 15:36:25

标签: c# sql entity-framework linq stored-procedures

道歉它可能是一个糟糕的头衔;我有一个问题比阴谋更多。

我已经用两种不同的方式测试了相同的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.Now它将代码转换为TSQL。

任何人都能解释为什么会这样吗? 另外,你认为更好的做法是什么?

提前致谢, 亚当

4 个答案:

答案 0 :(得分:3)

如果您在过滤器表达式中传递SYSDATETIME,则提供程序知道如何处理它并将其替换为对SYSDATETIME的调用,否则它别无选择,只能假设您正在寻找具体日期。这就是Expressions在C#(see here

中的工作方式

至于哪一个使用,那真的取决于你的用例。对[DataContract] public class FormulaStep : BaseData, ISearchable, ICloneable { ... } [DataContract] public class PreparationStep : FormulaStep, ISearchable, ICloneable { ... } 的调用将使用服务器上的时间,而后者将使用进行调用的机器上的时间。在实践中,这些可能是相同的,或者至少只是略有不同(考虑到网络延迟,时间漂移等)。

答案 1 :(得分:2)

您正在定义将在稍后执行的表达式。

第一个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();的代码行执行 由于您可以在程序中构建表达式并稍后执行它,因此LINQ必须使用带参数的查询来确保将正确的日期和时间传递给该查询,从而确定您定义的变量。 / p>

答案 2 :(得分:0)

嗯,在第一种情况下,它知道它正在使用当前系统时间因为你传递了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();

答案 3 :(得分:0)

这是因为解析表达式树以将LINQ语句转换为SQL。在使用变量存储DateTime的示例中,遍历的表达式是ParameterExpression,通过向查询添加参数将其转换为SQL。在LINQ查询中直接使用DateTime.Now的示例中,Expression的类型为MemberExpression,而实体框架的表达式树遍历器具有表示DateTime.Now的MemberExpression的特殊情况,该表示将其作为SysDateTime()转换为SQL

如果您的意图始终是在查询中使用当前数据库时间,我将直接在LINQ表达式中使用DateTime.Now。如果意图是在查询中使用Application Server上捕获的特定时间,我将使用变量。

This MSDN Blog全面解释了这是如何运作的。