LINQ to Entities表达式中的对象函数失败

本文关键字:对象 函数 失败 to Entities 表达式 LINQ | 更新日期: 2023-09-27 18:29:21

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

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中的函数。当我把它改成一个不存在歧义的自定义函数时呢。

即:

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对象。

LINQ to Entities表达式中的对象函数失败

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

这不起作用的原因是因为它在where子句中,而where子句必须在SQL中执行才能准确。它不能在后端的C#代码中执行,至少不能在不静默地返回表的所有行的情况下执行,这不是所需的功能。。。如果是,你可以明确地告诉它这样做。

Entity Framework提供了一组用于处理可以直接转换为SQL的日期的函数,这些函数位于EntityFunctions命名空间中。这些映射到所谓的"规范函数",也就是说有1:1的SQL转换。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

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

LINQ to 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没有一个与.NET AddDays()"完全"有效的AddDays()DATEADD使用整数,而不是浮点)。

LINQ到SQL的作用和EF的作用更正确吗?我会说EF的做法更正确(即使它更"奇怪")。。。

示例:如果.NET应用程序和SQL应用程序位于两个不同的时区(因此时间不同),会发生什么。。。DateTime.Now是.NET时间还是SQL时间更正确?我认为是第二个(但我重复一遍,如果我在我的应用程序中发现了这个"bug",即使我也会发出很大的ooooooh)。

顺便说一句(不是很重要),你不应该在同一个地方计算两次日期,然后认为它们是相等的。这一次你使用了完整的日期,所以没有问题,但如果你只使用DateTime.Now.Date,如果你的代码是在午夜左右执行的,也许,非常可能,这两个日期会不同,因为一个是在23:59:59.99999999计算的,而另一个是第二天00:00:00.000000计算的。

问题是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:用EntityFunctions.AddDays(DateTime.Now, 1); 替换DateTime.Now.AddDays(+1)

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

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

和在哪里:

.Where(e => 
    e.EventDate >= yesterday  && 
    e.EventDate <= tomorrow 
)