来自 Access ODBC 驱动程序的无效日期时间格式异常

Posted

技术标签:

【中文标题】来自 Access ODBC 驱动程序的无效日期时间格式异常【英文标题】:Invalid datetime format exception from Access ODBC Driver 【发布时间】:2013-12-06 15:31:16 【问题描述】:

我有一些从 ODBC 驱动程序读取的 .NET 代码(驱动程序是根据 DSN 字符串(使用用户名和密码)选择的),该驱动程序从表中读取多个字段,其中一个字段是 DateTime 字段。该代码 100% 可用于 SQL Server 数据库/ODBC 驱动程序,大部分时间可用于 MS Access 数据库。但是,有时我会在特定行上收到“第 2 列 (DateTimeColumn) 上的日期时间格式无效”异常,甚至没有直接访问该列(例如,即使我只是调用

reader.IsDBNull(someOtherColumn)

我仍然得到异常。

这似乎主要(仅?)发生在 Access 数据库已填充来自 Excel 的数据时,其中一些 DateTimes 被计算(例如,将 1/24 添加到日期时间以获得下一小时)。

如果我运行以下查询,异常就会消失:

UPDATE MyTable Set DateTimeColumn = CDate(CStr(DateTimeColumn))

因此,从 Excel 的日期时间计算到 Access 驱动程序的日期时间计算,似乎存在某种舍入误差。

由于其中一些数据是由创建自己的数据库的用户提供的,因此我无法使用我的代码在他们的数据库上运行 UPDATE 查询。一种可能的仅限访问的解决方法是在我的 SQL 语句中调用 CDate(CStr(DateTimeColumn)),但这不适用于 SQL Server 或其他数据库。

我只使用 32 位 MS Access 驱动程序(我的机器上没有 64 位驱动程序来测试它们)对此进行了测试,该驱动程序适用于 .mdb 文件和 .accdb 文件,但出现了问题无论数据是在 .mdb 文件还是 .accdb 文件中。

编辑:

为了将来参考,Access 数据库中导致异常的 Date/Time 值是 0x40E4277FFFFFFFF8,十进制是

41275.9999999999417923390865326

还可能令人感兴趣的是,虽然此值在尝试通过OdbcConnection 读取行时导致错误,但使用OleDbConnection 读取同一行没有引发异常。

【问题讨论】:

您能否隔离具有导致异常的日期值的记录,将其复制到单独的 Access 数据库文件,然后使该文件可供下载(例如,通过 wikisend.com 或类似方式) ?我只是花了几分钟用十六进制编辑器破解 .accdb 文件,试图创建“奇怪”的日期值,但 ACE ODBC 没有抱怨其中任何一个。 戈德,感谢您的宝贵时间。如果您能够访问已编辑问题中的链接,请告诉我。 【参考方案1】:

鉴于更新问题中的测试数据,以下解决方法似乎有效。它检查打开连接的.Driver 属性以查看它是否正在从 Access 数据库中读取。如果是这样,它会将Date/Time 值检索为Double,然后将其转换回System.DateTime。否则,它只是从 SQL Server 正常检索datetime

(请注意,这种特殊方法 - 使用 rdr[0]) * 86400 - 不会正确处理 Access 中早于 1899-12-30 00:00:00Date/Time 值,即当 Double 值是负数。)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Odbc;

namespace odbcTest

    class Program
    
        static void Main(string[] args)
        
            using (OdbcConnection con = new OdbcConnection())
            
                //con.ConnectionString =
                //        @"Driver=SQL Server;" +
                //        @"Server=(local)\SQLEXPRESS;" +
                //        @"Database=myDb;" +
                //        @"Trusted_connection=yes;";
                con.ConnectionString =
                        @"Driver=Microsoft Access Driver (*.mdb, *.accdb);" +
                        @"Dbq=C:\__tmp\dateTest\TestSqlRead.accdb;";
                con.Open();
                using (OdbcCommand cmd = new OdbcCommand())
                
                    DateTime dtm;
                    var accessTime0 = new DateTime(1899, 12, 30);
                    bool fromAccess = (con.Driver == "ACEODBC.DLL");
                    cmd.Connection=con;
                    if (fromAccess)
                        //cmd.CommandText = "SELECT DateTimeCol FROM MyTable";  // this fails
                        cmd.CommandText = "SELECT fn CDbl(DateTimeCol) FROM MyTable";
                    else
                        cmd.CommandText = "SELECT sqlDate FROM Table1 WHERE ID = 1";
                    OdbcDataReader rdr = cmd.ExecuteReader();
                    rdr.Read();
                    if (fromAccess)
                        dtm = accessTime0.AddSeconds(Convert.ToDouble(rdr[0]) * 86400);
                    else
                        dtm = Convert.ToDateTime(rdr[0]);
                    Console.WriteLine(dtm.ToString());
                    rdr.Close();
                
                con.Close();
            
            Console.WriteLine();
            Console.WriteLine("Done.");
            Console.ReadKey();
        
    

【讨论】:

使用 con.Driver(或 con.DataSource == "ACCESS")是我最初的想法,但我担心 ACEODBC.DLL 字符串可能会随着访问驱动程序版本而改变(64 位驱动程序具有相同的 .dll?)您是否选择 CDbl() 而不是 CDate(CStr()) 以获得性能?我担心如果从 Excel 复制到 Access 时出现舍入问题,那么 AddSeconds() 可能会在我认为第二天会得到的午夜之前给我。 @user3000697 是的,DLL 在 32 位和 64 位平台上都被命名为 ACEODBC.DLL。名称可以改变,但我怀疑它会改变。我选择了CDbl(),因为Access 在内部将日期存储为Double 数字。 CDate(CStr()) 是有风险的,因为在默认日期格式为 dd-mm-yyyy 的机器上,它可能会破坏模棱两可的日期,例如,4 月 2 日可能会神秘地变成 2 月 4 日。至于舍入错误,一旦你有 Double 值(没有你的代码炸毁)你可以根据需要摆弄它;我只使用了.AddSeconds(),因为这是最直接的方法。 CDate() 和 CStr() 对于 MS Access 真的不互惠吗?这太可怕了——我会假设转换一个方向会安全地转换回另一个方向。感谢您的帮助。 @Jeremy 从一开始 Jet/ACE 就总是将不明确的日期文字解释为mm-dd-yyyy,无论机器本身的区域设置如何。举个例子,看我的回答here。 如果我希望能够使用相同的 OdbcDataReader 代码并且只使用不同的 SELECT 语句(因此只有一个“if(fromAccess)”),您是否发现以下查询有任何问题:SELECT CDate(Round(CDbl(DateTimeCol),8)) (我不关心小于百分之一秒的时间分辨率)这似乎解决了问题。【参考方案2】:

没有。

Access 使用自己的日期格式

'#2011-12-16#'

SQL-Server 对日期值使用 ISO 标准:

20111216

2012-12-16THH:mm:ss.fff

日期时间

您可以做的是将带有日期值的表作为字符串提取到 System.Data.DataTable 中,更正这些值,然后使用更新将它们写回。更好的是,永远不要使用文本字段来存储日期/日期时间,而且你从一开始就不会遇到这个问题,因为它不允许你插入无效的日期时间值(嗯,也许 Access 可以......)。

此外,如果您对插入使用参数化查询,您一开始就不会遇到这个问题。

如果您从客户那里得到一个 Excel 文件,您需要验证 Excel 表中的日期值是否有效。 如果不是,则需要有人更正 Excel 文件中的数据。

日期必须始终采用相同的格式 (MM-DD-YYYY),有时可能不同 (dd.MM.yyyy) 日期必须是 > 最小值,如 SQL-Server 中的 01.01.1753(尤其重要,因为日期格式为 MM-DD-YY) SQL-Server 中的日期不得超过 31.12.9999 23:59:59.995

如果使用 access + SQL Server,则最小日期值必须是两个最小日期值中的最大值,最大日期值必须是两个最大日期值中的最小值。

【讨论】:

我是否误读了您的答案,还是您的大多数 cmets 似乎都认为我将 DateTime 存储为字符串,而不是 DateTime?您关于使用参数化查询的建议很好,但我认为 .mdb 文件不支持参数化查询——视图(在 Access 中保存的查询)是否也一样好? @user3000697 是的,您可以对 ACE/Jet (Access) 数据库运行参数化查询。 Access 中保存的查询可以用作参数查询,但这仅在使用 DAO 时才真正有用。对于 .NET 下的 ODBC,只需使用 OdbcCommand 对象并给它一些 .Parameters。这里有很多例子展示了如何使用 OleDbCommand 对象来做到这一点,OdbcCommand 对象的工作方式相同。 抱歉 - 我将参数化查询(我在 Access 中使用过)与存储过程混淆了,认为 Quandary 建议使用存储过程来处理 DateTime 字符串的不一致格式

以上是关于来自 Access ODBC 驱动程序的无效日期时间格式异常的主要内容,如果未能解决你的问题,请参考以下文章

在 pyodbc 中执行 SELECT 查询时,来自 ODBC MS Access 驱动程序的“系统资源超出”错误

[Access][Microsoft][ODBC 驱动程序管理器] 无效的字符串或缓冲区长度 Invalid string or buffer length

带参数的 PypyODBC:[ODBC Microsoft Access 驱动程序] 参数太少。预期 3(不是日期问题)

Linux 上的 SQL Server:来自 Windows 的 ODBC 连接有效,应用程序无效

MS Access 错误“ODBC--调用失败。转换规范 (#0) 的字符值无效”

为什么我的SQL Server审核触发器搞乱了来自Access的ODBC调用/刷新?