【问题标题】:Encoding problem when migrating a legacy tool to a new Windows version with OleDB使用 OleDB 将旧工具迁移到新的 Windows 版本时出现编码问题
【发布时间】:2019-09-30 11:00:27
【问题描述】:

将用 c# 编写的旧应用程序(>15 年)迁移到新的 Windows Server 后,我们遇到了一个奇怪的问题。 该应用程序使用 OleDB 连接到 Informix 数据库。该数据库有一个包含多种语言文本的表格。在 Windows 2003 服务器中运行的应用程序运行良好,但是在新的 Windows 2016 中它会引发错误: “由于符号不匹配或数据溢出以外的原因,无法转换数据值。例如,数据存储中的数据已损坏,但该行仍可检索。”

经过一番调查,我们发现问题出在包含一些 unicode 字符的字符串中。

这是产生问题的部分文字(仅说明问题的部分文字:

“17”-Leichtmetallräder ...... Ziffern - Schaltknauf”

这是一个德语文本,看起来还可以,问题实际上出在“-”上。查看 Hex 中的 db 记录,第一个“-”编码为“3F”,但第二个破折号编码为“C296”,对应于 U+0096(unicode 中的破折号)

DB 的设置是 en_US.819(对应 ISO-8859-1 以支持所有需要支持的语言)。

现在的问题是,当在 Windows 2003 中运行程序时,结果被正确地写入了一个文件,例如:

“17”-Leichtmetallräder ...... Ziffern - Schaltknauf”

但是在 Windows 2016 中,会引发上述异常并且没有写入任何内容。

我进行了一些代码更改,我做的第一件事是更改 OleDB for Odbc 连接并且异常消失了,但是输出中的文本不正确:

“17”-Leichtmetallräder ......齐弗恩?沙尔特克瑙夫"

注意使用 odbc 连接的相同代码如何无法理解 unicode 破折号。

这是适用于 Windows 2003 的 OleDB 代码:

OleDbConnection ConnOleDbIDD = new OleDbConnection("Provider=Ifxoledbc.2;Data Source=db;INFORMIXSERVER=localhost;IFMX_UNDOC_B168163=1;"); string sConnectTemplateDB = "数据源 = SQLServerDB;初始目录 = DB1;连接超时 = 28800;集成安全 = True"; ConnOleDbIDD.Open(); sExportSQL = "SELECT * From MyTable"; OleDbCommand cmdIDD = new OleDbCommand(sExportSQL, ConnOleDbIDD); cmdIDD.CommandTimeout = 28800; SqlDataAdapter da; ConnSchemaIDD = 新的 SqlConnection (sConnectTemplateDB); ConnSchemaIDD.Open(); SqlCommand cmdSQLServerTemplate = new SqlCommand(sExportSQL.Replace("TRIM","LTRIM"), ConnSchemaIDD); cmdSQLServerTemplate.CommandTimeout = 28800; da = new SqlDataAdapter(cmdSQLServerTemplate); OleDbDataReader 博士; 数据集 ds = new DataSet(); da.MissingSchemaAction = MissingSchemaAction.AddWithKey; da.Fill(ds, sSourceTable); 数据表 dt = ds.Tables[sSourceTable]; 博士 = cmdIDD.ExecuteReader() iEnCodingFrom = 1252; iEnCodingTo = 1252; 而 (dr.Read()) { sValue = ""; sCurrentValue = ""; bDelimiterPosition = 假; foreach(dt.Columns 中的 DataColumn cCol) { 对象椭圆 = dr.GetValue(dr.GetOrdinal(cCol.ColumnName)); 字符串 val = Convert.ToString(dr[cCol.ColumnName]); sCurrentValue = System.Text.Encoding.GetEncoding(iEnCodingTo).GetString(System.Text.Encoding.Convert(System.Text.Encoding.GetEncoding(iEnCodingFrom), System.Text.Encoding.GetEncoding(iEnCodingTo), System.Text.Encoding .GetEncoding(iEnCodingFrom).GetBytes(val))); if (bDelimiterPosition == true) { sValue = sValue + sDelimiter + sCurrentValue.Trim(); } 别的 { sValue = sValue + sCurrentValue.Trim(); } bDelimiterPosition = true; } w.WriteLine(sValue); w.Flush(); } 博士关闭();

假设此示例“Mytable”有 2 列,第一列是整数 ID,第二列是 char(3100)。

如您所见,代码做了一些奇怪的事情,例如从 SQLServer 数据库中的表架构中获取列描述,以及将 db 输出从 CP1252 转换为 CP1252。我不知道为什么它是这样编码的。 我对这个问题的解决方法是对代码进行这些更改(使用 odbc 连接而不是 oledb):

iEnCodingFrom = 28591; ... sCurrentValue = Encoding.GetEncoding(iEnCodingTo).GetString(Encoding.GetEncoding(iEnCodingFrom).GetBytes(val.ToCharArray())); ...

因此,将连接更改为与 informix DB 的 ODBC 连接以防止引发异常,并从代码页 28591 (8859-1) 转换为 1252 (CP1252) 在 Windows 2016 中产生与旧版本相同的结果Windows 2013 中的代码。

所以我有一个解决方法并且可以使用它,但是我想了解为什么会发生这种情况,为什么我不能继续使用 OleDB 以及是否有办法让它在新的 Windows 环境中工作(也失败了在 Windows 10 中),无需更改代码。

任何帮助将不胜感激。

谢谢

【问题讨论】:

  • 澄清:U+0096 在 Unicode 中不是破折号,而是一个控制字符。 0x96 是 Windows-1252 中的破折号。
  • 感谢您的澄清,它确实是一个控制字符,在 UTF-8 中是 0xc2 0x96 并且根据此来源fileformat.info/info/unicode/char/96/index.htm 显然打印为破折号@对不起,我不是专家在编码方面。
  • 可能是应用程序将 UTF-8 代码点插入“en_US.819” Informix 数据库(Informix 不会抱怨,它只是一个字节流)。您应该使用正确的 db_locale 和 client_locale 配置驱动程序,以便它不再返回代码页转换错误。
  • 感谢@LuísMarques 提供的信息。我检查了这些值是否未在旧的 Windows 2003 服务器中设置。尽管如此,我还是试了一下。使用驱动程序配置工具 setnet32,db_locale 唯一可能的值是 819(否则语言环境不匹配,它不会让我连接),在 client_locale 中我尝试了几个值,如 1252、819、utf8,但结果始终是同样的错误。知道这种情况下的正确配置是什么,为什么在旧的 Windows 服务器中不需要它?
  • IFMX_UNDOC_B168163 在连接字符串中使用可能会产生一些影响(它使 Informix 对客户端/数据库区域设置不那么严格),但是现在,您能否确认错误来自驱动程序还是来自字符串编码部分,已经在应用程序中了吗?

标签: unicode encoding oledb informix


【解决方案1】:

感谢@LuísMarques 和@jsagrera 这正是我正在寻找的解释,所以现在我可以理解问题了。文章中说:

“从 CSDK 2.80 版开始,ODBC 驱动程序支持 Unicode,这意味着驱动程序处理的所有数据都必须是 Unicode 格式。这意味着必须进行额外的转换”。

老服务器的csdk版本是2.71。新服务器中的版本是4.10。

现在,“UNDOC”因此存在,数据库是使用 en_us.819 创建的,但我的客户端应用程序的“undoc”变量忽略了它,它假设数据来自 CP1252 并打印出来CP1252,没有任何内部转换程序就可以工作。

但无论如何,数据库中的数据已损坏。升级驱动程序后,内部转换会产生错误。

我仍然可以解决它,我不在 ODBC 连接中使用“UNDOC”,然后我从数据库中获取字节流,并在我的 C# 代码中进行从 8859-1 到 CP1252 的转换。这样我得到与旧服务器完全相同的输出。

但这不是正确的解决方案,而是缓解问题,最终的解决方案是将数据库更改为 UTF8 以避免更多问题。这就是我们最终要做的。

谢谢@jsagrera 我想将您的答案标记为正确答案。我是这个平台的新手,所以我不太清楚它是如何工作的。如果您将评论作为答案发表,我很乐意将其标记为正确。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 2012-02-19
    • 2013-01-02
    • 1970-01-01
    • 2016-10-25
    • 2020-11-21
    相关资源
    最近更新 更多