7

请注意:我知道如何解决这个问题。我不是在寻找解决方案,而是在寻找问题本身的清晰度。

class Program
{
    static void Main(string[] args)
    {
        using (var context = new TestDbContext())
        {
            var eventsFound = context.Events
                .Where(e => 
                    e.EventDate >= DateTime.Now.AddDays(-1) && 
                    e.EventDate <= DateTime.Now.AddDays(+1)
                )
                .ToList();
        }
    }
}

public class TestDbContext : DbContext
{
    public DbSet<Event> Events { get; set; }
}

public class Event
{
    public int EventId { get; set; }
    public DateTime EventDate { get; set; }
}

好的,所以上面的程序失败了:

LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)'
method, and this method cannot be translated into a store expression.

为什么 LINQ 不能区分数据库函数和对象函数。系统应该足够聪明地意识到 AddDays 函数是 DateTime 对象的一部分。然后它应该首先解析该函数​​,然后在解析查询中的所有函数后,转换为 SQL 并针对数据库执行该函数。

我敢肯定它比这复杂得多,但我想了解原因。

========= 编辑 ===============

所以上面实际上并不是一个很好的例子,因为“AddDays”是一个存在于 .NET 和 SQL 中的函数。当我将其更改为不存在歧义的自定义函数时会怎样。

IE:

public class Event
{
    public int EventId { get; set; }
    public DateTime EventDate { get; set; }

    public DateTime ReturnDateNowExample()
    {
        return DateTime.Now;
    }
}

static void Main(string[] args)
{
    var myEvent = new Event {EventDate = new DateTime(2013, 08, 28)};
    using (var context = new TestDbContext())
    {
        var eventsFound = context.Events
            .Where(e =>
                e.EventDate >= myEvent.ReturnDateNowExample()
            )
            .ToList();
    }
}

如果是不明确的 DateTime 对象,则替换为 string/int 对象。

4

4 回答 4

10

其原因与其“聪明”无关,更多的是与 Linq 的工作方式有关。Linq 使用一种叫做“表达式树”的东西。基本上,它将您的表达式编译为一组数据,然后由翻译层将其转换为 SQL。

这不起作用的原因是因为这是在 where 子句中,并且 where 子句必须在 SQL 中执行才能准确。它不能在后端的 C# 代码中执行,至少不能在没有静默返回表的所有行的情况下执行,这不是所需的功能……如果是,您可以告诉它显式执行此操作。

Entity Framework 提供了一组函数来处理可以直接转换为 SQL 的日期,这些函数位于 EntityFunctions 命名空间中。这些映射到所谓的“规范函数”,这意味着对 SQL 有 1:1 的翻译。Linq to Sql 将客户端评估的 where 子句作为参数传递,但这可能是也可能不是所需的值,因为您可能需要服务器端的值而不是客户端计算的值。因此 L2S 会给您带来意想不到的结果结果在某些情况下。

简而言之,您需要特殊的表达式函数才能转换为 SQL,而不幸的是,任何旧的标准 .NET 类都不起作用,而 DateTime 类就是这样。

您可能会发现以下文章很有用:

http://blogs.msdn.com/b/charlie/archive/2008/01/31/expression-tree-basics.aspx

http://tomasp.net/blog/linq-expand.aspx/

http://social.msdn.microsoft.com/Forums/en-US/21a9c660-13e5-4751-aa51-6519bddae044/enterprise-framework-linq-queries-failing

于 2013-08-28T08:15:06.153 回答
6

有趣的是,如果我们直接DateTime.Now在查询中使用 LINQ-to-SQL 和实体框架生成的不同查询:

LINQ 到 SQL:

WHERE ([t0].[EventDate] >= @p0) AND ([t0].[EventDate] <= @p1)

实体框架

WHERE ([Extent1].[EventDate] >= CAST( SysDateTime() AS datetime2)) AND ([Extent1].[EventDate] <= CAST( SysDateTime() AS datetime2))

此处的区别在于 LINQ-to-SQL 考虑DateTime.Now必须在 .NET 端计算并作为查询参数发送的内容,而 EF 考虑DateTime.Now可以在 SQL 端计算的内容。从这一点很明显,我们在 LINQ-to-SQL 中DateTime.Now.AddDays()“有效”(因为表达式的那部分在 .NET 端被完全评估),而在 EF 上却没有,因为 SQL 没有AddDays()有效的“完全”作为.NET AddDays()DATEADD使用整数,而不是浮点数)。

LINQ-to-SQL 做什么或 EF 做什么更正确?我会说 EF 所做的更正确(即使它更“奇怪”)......

示例:如果 .NET 应用程序和 SQL 应用程序位于两个不同的时区(因此具有不同的时间)会发生什么... DateTime.Now.NET 时间或 SQL 时间会更正确吗?我认为是第二个(但我再说一遍,如果我在我的应用程序中发现这是一个“错误”,即使我也会大发雷霆)。

作为旁注(不是很重要),您不应该在同一个地方计算两次日期并认为它们是相等的。这次你使用了完整的日期,所以没有问题,但是如果你只使用了DateTime.Now.Date,并且如果你的代码是在午夜左右执行的,那么两个日期可能会不同,因为一个是在 23:59 计算的: 59.9999999,而另一个在第二天的 00:00:00.0000000 计算。

于 2013-08-28T08:56:18.463 回答
4

问题是 EF 正在尝试转换然后在SQL一边执行您的查询。那里没有System.DateTime.AddDays等价物。

所以DateTime.AddDays方法既不是规范也不是数据库函数,不能转换为适当的命令树节点以供进一步执行。通常,您应该在查询中使用SqlFunctionsEntityFunctions但是仍然有一种方法可以通过在.edmx文件 中定义自定义数据库函数来调用它们。

还要考虑到LINQ to Entities 支持某些标准查询方法:AggregateLast等以及许多重载,例如Select<TSource, TResult>(IQueryable<TSource>, Expression<Func<TSource, Int32, TResult>>).
支持的运算符的完整列表是在这里


要解决此问题,您应该使用EntityFunctions.AddDays:替换DateTime.Now.AddDays(+1)EntityFunctions.AddDays(DateTime.Now, 1);

于 2013-08-28T07:59:54.070 回答
0

您也可以将这些天数存储在一个变量中:

DateTime yesterday = DateTime.Now.AddDays(-1);
DateTime tomorrow = DateTime.Now.AddDays(+1);

在哪里:

.Where(e => 
    e.EventDate >= yesterday  && 
    e.EventDate <= tomorrow 
)
于 2013-08-28T08:03:04.500 回答