【问题标题】:How to migrate datetime to datetime2 with new SQL Server Native Client如何使用新的 SQL Server Native Client 将 datetime 迁移到 datetime2
【发布时间】:2026-01-07 23:45:02
【问题描述】:

我们目前正在将我们的数据库从 datetime 迁移到 datetime2,包括 SQL Server Native Client v11 (SQL Server 2012)。数据库更新很容易完成,但问题出现在我们想要使用的新 SQL Server Native Client 11 上。

一般来说,我们的 CRUD 操作都有带有“COLUMN_ENTRY*”访问器的 OLE DB 消费者。对于datetime2 列,成员的类型为DBTIMESTAMP。使用SQLNCLI 提供程序,DBTIMESTAMP 的小数部分被静默截断为支持的值。使用 SQLNCLI11 插入的分数太精确会导致此错误:

DB_E_ERRORSOCCURRED Multiple-step OLE DB operation generated errors. Check each OLE DB status value, if available. No work was done.

根据this link,如果向字段中插入的数据过多,则会返回此错误。由此我假设 DBDATETIME 成员的小数部分太精确而无法插入。根据this link 的说法,新的 Native Client 版本(10 和 11)不会截断,但会因错误而失败。以我们想要实现的这个简化示例为例:

class CMyTableStruct
{
public: 
    CMyTableStruct();
    LONG m_lID;
    DBTIMESTAMP m_dtLogTime;
};
class CMyTableStruct_InsertAccessor : public CMyTableStruct
{
public:
    BEGIN_PARAM_MAP(CMyTableStruct_InsertAccessor)
        COLUMN_ENTRY(1,  m_dtLogTime)
    END_PARAM_MAP()  
};

在代码的某些部分,我将时间戳初始化为 2015-08-10 07:47:49.986149999 并且插入失败。如果我将分数重置为 0,则插入有效; 0 以外的任何值都失败。我尝试使用 COLUMN_ENTRY_PS 提供具有各种值的日期时间精度和比例,但插入总是失败。

我们如何强制 Native Client 简单地接受该值并截断它?很明显,我们不能手动将所有值截断为支持的 DB 精度。我找不到任何关于如何在新的 Native Client 中使用 datetime2 的适当文档。我们是否缺少正确处理 datetime2 的任何转换或设置?

这是测试设置:

  • Windows 7 64 位
  • SQL Server Native Client 11 (SQLNCLI11)
  • SQL Server 2012
  • DBPROP_INIT_LCID=2055
  • DBPROP_INIT_PROMPT=4
  • DBPROP_INIT_DATASOURCE=localhost
  • DBPROP_AUTH_INTEGRATED=SSPI

对于SQLNCLI 提供程序,相同的代码可以工作。

[EDIT#1]:我使用 AtlTraceErrorRecords 转储了更多错误信息,这证实了与链接的 Microsoft Connect 报告中相同的错误:

Row #: 0 Source: "Microsoft SQL Server Native Client 11.0" Description: "The fractional part of the provided time value overflows the scale of the corresponding SQL Server parameter or column. Increase bScale in DBPARAMBINDINFO or column scale to correct this error." Help File: "(null)" Help Context: 0 GUID: {0C733A63-2A1C-11CE-ADE5-00AA0044773D}

[EDIT#2]:根据我的进一步研究,这似乎是新 Native Client 的定义和接受的行为。请参阅Stricter SQL_C_TYPE _TIMESTAMP and DBTYPE_DBTIMESTAMP parameter validation 以供参考。但是如果没有任何文档说明如何正确使用 datetime2,微软就无法认真对待这一变化。他们真的需要开发人员自己在将 DBTIMESTAMP 插入数据库之前对其进行四舍五入吗?在某些情况下,由于某些浮点精度错误,舍入甚至不起作用。以上面的示例时间戳为例,您将看到典型的 .9999 精度错误。有人应该如何让这种奇怪的行为起作用?使用此错误消息,您甚至无法在不使用now() 的 SQL 函数的情况下将当前时间戳保存到数据库,因为您通常会在这些溢出中运行。

【问题讨论】:

    标签: c++ sql-server oledb datetime2 sql-server-native-client


    【解决方案1】:

    我做了很少的测试并完全重写了我的答案。

    我使用 SQL Server 2008 和 Native Client 10。它已经支持datetime2(7),所以我的测试是适用的。

    日期时间

    我有一个具有表值参数的存储过程。参数表的其中一列具有datetime 类型。我之前没有使用非零分数,但现在尝试了。我花了一段时间才弄清楚需要什么。

    为了在DBTIMESTAMP.fraction 字段中使用非零值,我必须同时配置列描述和参数描述:将DBCOLUMNDESC.bScale 设置为3,将DBBINDING.bScale 设置为3。bPrecision 都保留为0。这个MSDN doc 帮助我意识到我也必须设置DBCOLUMNDESC.bScale

    SQL Server Native Client OLE DB 提供程序检查 DBCOLUMDESC bScale 成员确定小数秒精度。

    一旦 Scale 设置为 3,我可以将 DBTIMESTAMP.fraction 的值设置为 555000000,并将其作为 .557 成功插入数据库,即服务器将值进一步四舍五入到 1/3毫秒(datetime 类型的精度)。 但是,当我尝试将 DBTIMESTAMP.fraction 的值设置为 552100000 时,我遇到了与您遇到的相同的错误。因此,服务器希望程序员自己对值进行四舍五入。

    截断分数的一种简单方法是这样的:

    DBTIMESTAMP dt;
    // set to any value from 0 to 999,999,999 (billionth of a second)
    dt.fraction = 555123456; 
    
    // truncate fractions to milliseconds before inserting into the database
    dt.fraction /= 1000000;
    dt.fraction *= 1000000;
    

    datetime2(7)

    我将表值参数中的列类型更改为datetime2(7),并进行了一些测试。

    我将 Scale 设置为 7,并且可以成功地将值 DBTIMESTAMP.fraction 插入数据库 555123400,但是当我尝试插入值 555123478 时,我得到了同样的错误。因此,对于datetime2(7),服务器还希望程序员自己对值进行四舍五入。

    // truncate fractions to 100 nanoseconds precision
    dt.fraction /= 100;
    dt.fraction *= 100;
    

    应该够了。

    【讨论】:

    • 是的,如果我将精度设置为 7,我会得到同样的错误。我尝试使用荒谬的高值 (>50) 来使 Native Client 接受任何巨大的值,但这些插入也失败了。 Native Client 无法将这些详细的分数转换为 datetime2(7) 而不会导致此错误的截断。关于手动截断你是对的,理论上应该可以做到。但是几乎不可能更改和维护代码中的所有位置以便在设置时间戳后截断分数。有没有办法在 oledb/ATL 级别自动执行此操作?
    • 好吧,如果我对Connect article 的理解正确,MS 说对于datetime,分数应该可以被1,000,000 整除。所以,对于datetime2(7),它应该能被100整除。我将在您班级的相应 getter 或 setter 中引入这种舍入/截断。 ULONG fraction = fraction / 100 * 100
    • 根据您在问题中包含的链接,似乎是的,他们现在确实需要开发人员自己在将DBTIMESTAMP 的小数部分插入数据库之前对其进行四舍五入。无论是 3、7 还是任何其他精度。此外,您必须在DBPARAMBINDINFOPrecisionScale 字段中指定您选择的精度。
    • 我发现我们有一些很好的应用时间来进行 DBTIMESTAMP 转换(即使它们没有在任何地方使用)。当我添加一些舍入(将最低 2-3 位清除为 0)时,我将做一些测试插入的行为,并会更新它的行为。
    • 我做了一些测试,完全重写了我的答案。
    【解决方案2】:

    将 C# 或 VB 与 OleDb 库一起使用时,请设置 OleDbParameter.Scale 值以避免出现此错误消息。

    使用 Provider=SQLNCLI11 测试。

    【讨论】: