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

时间:2013-08-28 07:55:31

标签: c# sql linq linq-to-entities

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

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并对数据库执行该函数。

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

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

所以上面并不是一个很好的例子,因为&#34; AddDays&#34;是一个存在于.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对象不明确,则用字符串/ int对象替换。

4 个答案:

答案 0 :(得分:8)

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

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

实体框架提供了一组函数,用于处理可以直接转换为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

答案 1 :(得分:6)

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

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时间?我想第二个(但我重复一遍,如果我在我的应用程序中发现这是一个“错误”,即使我会做一个大的ooooooh)。

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

答案 2 :(得分:4)

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

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

另外还要考虑 LINQ to Entities 不支持支持某些标准查询方法:AggregateLast等等,以及大量重载比如Select<TSource, TResult>(IQueryable<TSource>, Expression<Func<TSource, Int32, TResult>>)
支持的运营商的完整列表是here


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

答案 3 :(得分:0)

您可以将这几天存储在变量中:

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

并在Where:

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