Entity Framework 和 DateTime 的毫秒精度比较

Posted

技术标签:

【中文标题】Entity Framework 和 DateTime 的毫秒精度比较【英文标题】:Entity Framework and DateTime comparison with millisecond precision 【发布时间】:2012-10-13 14:39:24 【问题描述】:

我在 C# 下的实体框架(代码优先)中遇到了关于比较 DateTime 值的问题。我使用下面定义的类 Validity(在此示例中进行了简化)作为其他实体的超类,这些实体应及时具有定义的有效性。

public abstract partial class Validity 
    [Key]
    public int ID  get; set; 

    public DateTime? ValidFrom  get; set; 
    public DateTime? ValidTo  get; set; 

    /**
     * @brief This method builds an IQueryable from another IQueryable,
     * with added restriction on ValidityFrom/To
     *
     * An object's validitiy is defined to
     *   1. start at timestamp ValidFrom (=inclusive) and
     *   2. to end before ValidTo (=exclusive).
     *   3. If ValidFrom or ValidTo is NULL, it means to be "unbounded"
     *      in start or end time (respectively)
     * 
     **/
    public static IQueryable<T> isValidAt<T>(IQueryable<T> query, DateTime time) where T : Validity
    
        return query.Where<T>(c => 
               (!c.ValidFrom.HasValue || time >= c.ValidFrom)  // If ValidFrom != NULL, the given timestamp must be equal or "after" ValidFrom
            && (!c.ValidTo.HasValue || time < c.ValidTo));     // If ValidTo != NULL, the given timestamp must be "before" ValidTo
    

    /**
     * @brief Shall invalidate the object at timestamp time (implicitly sets validTo attribute).
     **/
    public void inValidate(DateTime time)
    
        ValidTo = time;
    


public class Item : Validity 
    public string property  get; set; 

在最后三行,您将找到我们将作为示例的“Item”类。让我们看看这个查询:

DateTime requestTime = DateTime.Now;
var items = from n in Validity.isValidAt(db.Items, requestTime)
            select n;

这个查询应该只返回在“requestTime”时“有效”的 Item 类的返回对象。请注意,对于 ValidTo == requestTime,项目将被视为“无效”(ValidFrom 到 ValidTo 的时间跨度是 -exclusive-ValidTo;请参见上面源代码中的 cmets)。

问题

我实际上 - 拥有 - 导致我的结果集“项目”具有 ValidTo == requestTime。 我刚刚通过

检查了这个
Item i= items.FirstOrDefault();
if ((i.ValidFrom.HasValue && i.ValidFrom > requestTime)
 || (i.ValidTo.HasValue && requestTime >= i.ValidTo)) 

   // ... SOME ERROR OUTPUT ...


** 注意:这个错误不会很少发生,但在软件中几乎一直都是 .inValidate(requestTime);通常调用来使对象无效。 **

我使用 LinQ 生成的 SQL 查询通过 Microsoft SQL Server Management Studio(Microsoft SQL Server 2008 用作后端)手动检查。我必须自己声明/设置@p__linq__0、@p__linq__1(这两者都意味着 requestTime)...

DECLARE @p__linq__0 DATETIME
DECLARE @p__linq__1 DATETIME
SET @p__linq__0 = '2012-10-23 15:15:11.473'
SET @p__linq__1 = '2012-10-23 15:15:11.473'

这实际上按预期工作。但是如果我使用 '2012-10-23 15:15:11' 作为值,我会收到错误的结果(如预期的那样)。它们与我的程序中的相似。所以我想这就是问题所在......

在数据库中,“DateTime”定义了毫秒,ValidFrom/ValidTo 被存储,包括毫秒。但我假设查询不包括时间戳的毫秒部分,出于任何原因...变量 requestTime 如何设置毫秒值。

不幸的是,我不知道如何检查查询中发送的实际值来验证这一点。我只知道如何使用items.toString()-方法输出生成的SQL,里面包含占位符。

我试过了: 1. db.Log = Console.Out; 由于“db.Log”不会被定义的错误而没有编译(自动完成也没有提示“Log”)。而 db 是从 DbContext 派生的。 2. 也将“项目”转换为 ObjectQuery,然后使用 .ToTraceString() 不起作用,程序在运行时崩溃并显示转换无效的错误消息。

如果这很重要:我使用 .NET 4.0 和 EntityFramework.5.0.0。

问题

    如何记录/输出完整的 SQL(包括占位符的值)? 如何以优雅的方式解决该问题? ...我并不是说只是从 inValidate() 中分配给“ValidTo”的“时间”中减去一秒的黑客攻击!

最好的问候,

斯蒂芬

编辑(发现更多细节)

我通过 SQL 分析器检查了发生的情况,这似乎很好。查询时正确提供具有高(7 位)精度的时间戳。但是:我没有得到导致错误结果的 SELECT 。所以我猜测:它一定是一些缓存。所以我在我的 LINQ 查询之前直接放了一个db.SaveChanges();。现在我在分析器中得到了所有查询。

我尝试了以下代码来更改数据库中的数据类型。正如 Slauma 所建议的那样(参见 https://***.com/a/8044310/270591)。

modelBuilder.Entity<Item>().Property(f => f.ValidFrom)
  .HasColumnType("datetime2").HasPrecision(3);
modelBuilder.Entity<Item>().Property(f => f.ValidTo)
  .HasColumnType("datetime2").HasPrecision(3);

我在重启前删除了整个数据库...

结果:使用 HasPrecision(x) 没有成功;其中 x 是 0、3 之一; (直接在前面有或没有 db.SaveChanges());但是:x = 7 与 db.SaveChanges() 相当有效;直接在查询之前...

所以,不幸的是,这个问题仍然存在......

当前的解决方法

我将以下方法应用于任何 DateTime 值,然后再将其分配给数据库对象属性。它只是将 DateTime 舍入到整秒精度(我在数据库中配置)。这也适用于任何用于比较的 DateTime。

结果:这更像是一个 hack,而不是一个解决方案!我需要为所有 setter 方法编写访问函数,这样就不会意外发生直接赋值。

    public static DateTime DateTimeDBRound(DateTime time) 
        DateTime t = time;
        long fraction = (t.Ticks % TimeSpan.TicksPerSecond);
        if (fraction >= TimeSpan.TicksPerSecond / 2)
        
            t = t.AddTicks(TimeSpan.TicksPerSecond - fraction);
        
        else
        
            t = t.AddTicks(-fraction);
        
        return t;
    

【问题讨论】:

Entity Framework losing Sql DateTime precision的可能重复 即使提到的文章似乎是关于同样的问题,它的解决方案不起作用。没有 .edmx 文件。我猜是因为我使用 Code First 方法。无论如何,我将更详细地研究这篇文章的解决方案。如果能以某种方式解决,我会确认删除请求(或自己删除)。 参数requestTime实际上应该以比毫秒高得多的精度传递,即datetime2(7)(即100皮秒精度):***.com/a/11620980/270591这个链接谈到了当你存储 .NET DateTime。但是您的结果很奇怪,在 DB x&lt;y 中不应该发生这种情况,但是对于内存中的物化结果 x&gt;=y,这对我来说听起来很糟糕。您可以尝试使用 datetime2(7) 作为 DB 类型(这是 .NET DateTime 的确切表示)而不是 datetime,但在我看来这不是必需的。 如果您不知道如何使用 EF 代码优先将 DateTime 属性映射到 datetime2(7) 列类型:***.com/a/8044310/270591 您是否真的尝试过将 DateTime 的精度设置为文章中提到的 3?您可以使用 HasPrecision Fluent Api 方法 (msdn.microsoft.com/en-us/library/…) 在覆盖的 OnModelCreating 方法中执行此操作。这里是解释使用 Fluent API 配置模型的链接。 msdn.microsoft.com/en-US/data/jj591617 【参考方案1】:

问题 1 如何记录/输出完整的 SQL(包括占位符的值)? 我认为最好的方法是使用 SQL Server 分析器。它显示所有语句和值。 或http://www.hibernatingrhinos.com/products/EFProf

我不知道提取执行命令的其他方法。

【讨论】:

以上是关于Entity Framework 和 DateTime 的毫秒精度比较的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 和 Sqlite

Entity Framework 和 Sqlite

Entity Framework 中 IMigrationMetadata 接口的用途和语义

Entity Framework Code First 迁移 Migrations

Entity Framework 学习日记

MVC5 Entity Framework学习之Entity Framework高级功能