【发布时间】: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