使用 OleDB 将遗留工具迁移到新的 Windows 版本时出现编码问题
Encoding problem when migrating a legacy tool to a new Windows version with OleDB
将用 c# 编写的旧应用程序(>15 岁)迁移到新的 Windows 服务器后,我们遇到了一个奇怪的问题。
该应用程序使用 OleDB 连接到 Informix 数据库。该数据库有一个 table 包含多种语言的文本。 Windows 2003 服务器中的应用程序 运行 工作正常,但是在新的 Windows 2016 中它会引发错误:
"The data value could not be converted for reasons other than sign mismatch or data overflow. For example, the data was corrupted in the data store but the row was still retrievable."
经过一些调查,我们发现问题出在一个包含一些 unicode 字符的字符串中。
这是产生问题的部分文字(仅部分文字说明问题:
“17”-Leichtmetallräder ...... Ziffern - Schaltknauf
这是德语文本,看起来不错,问题实际上出在“-”上。查看十六进制的db记录,第一个“-”编码为“3F”,但是第二个破折号编码为"C296",对应于U+0096(unicode中的破折号)
DB的设置为en_US.819(对应ISO-8859-1,支持所有需要支持的语言)。
现在,问题是当 运行 Windows 2003 中的程序时,结果正确地写入文件中,如:
“17”-Leichtmetallräder ...... Ziffern - Schaltknauf
但是在 Windows 2016 年出现了上述异常,但没有写入任何内容。
我进行了一些代码更改,我做的第一件事是为 Odbc 连接更改 OleDB,异常消失了,但是输出中的文本不正确:
“17”-Leichtmetallräder ...... Ziffern?沙尔特克瑙夫
注意使用 odbc 连接的相同代码如何无法理解 unicode 破折号。
这是适用于 Windows 2003 的 OleDB 代码:
OleDbConnection ConnOleDbIDD = new OleDbConnection("Provider=Ifxoledbc.2;Data Source=db;INFORMIXSERVER=localhost;IFMX_UNDOC_B168163=1;");
string sConnectTemplateDB = "Data Source=SQLServerDB;Initial Catalog=DB1; Connect Timeout = 28800; Integrated Security=True";
ConnOleDbIDD.Open();
sExportSQL = "SELECT * From MyTable";
OleDbCommand cmdIDD = new OleDbCommand(sExportSQL, ConnOleDbIDD);
cmdIDD.CommandTimeout = 28800;
SqlDataAdapter da;
ConnSchemaIDD = new SqlConnection (sConnectTemplateDB);
ConnSchemaIDD.Open();
SqlCommand cmdSQLServerTemplate = new SqlCommand(sExportSQL.Replace("TRIM","LTRIM"), ConnSchemaIDD);
cmdSQLServerTemplate.CommandTimeout = 28800;
da = new SqlDataAdapter(cmdSQLServerTemplate);
OleDbDataReader dr;
DataSet ds = new DataSet();
da.MissingSchemaAction = MissingSchemaAction.AddWithKey;
da.Fill(ds, sSourceTable);
DataTable dt = ds.Tables[sSourceTable];
dr = cmdIDD.ExecuteReader()
iEnCodingFrom = 1252;
iEnCodingTo = 1252;
while (dr.Read())
{
sValue = "";
sCurrentValue = "";
bDelimiterPosition = false;
foreach (DataColumn cCol in dt.Columns)
{
object oval = dr.GetValue(dr.GetOrdinal(cCol.ColumnName));
string 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();
}
else
{
sValue = sValue + sCurrentValue.Trim();
}
bDelimiterPosition = true;
}
w.WriteLine(sValue);
w.Flush();
}
dr.Close();
假设此示例 "Mytable" 有 2 列,第一列是整数 ID,第二列是 char(3100)。
如您所见,代码做了一些奇怪的事情,例如从 SQLServer 数据库中 table 的模式获取列描述,以及将 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 中也失败)而无需更改代码。
如有任何帮助,我们将不胜感激。
谢谢
感谢@LuísMarques 和@jsagrera 这正是我正在寻找的解释,所以现在我可以理解问题所在了。在文章中说:
"Since CSDK version 2.80, the ODBC driver is Unicode enabled, this means all the data the driver handles has to be in Unicode format. This means that a extra conversion has to be done"。
老服务器的csdk版本是2.71。新服务器的版本是4.10.
现在,由于这个原因,"UNDOC" 在那里,数据库是使用 en_us.819 创建的,但是我的客户端应用程序的 "undoc" 变量忽略了它,它假设数据进入 CP1252 并在 CP1252 中打印出来,程序无需任何内部转换即可运行。
但是数据库中的数据无论如何都已损坏。升级驱动程序后,进行内部转换会产生错误。
我仍然可以解决它,我不在 ODBC 连接中使用 "UNDOC",然后我从数据库中获取字节流并在我的数据库中进行从 8859-1 到 CP1252 的转换C# 代码。这样我得到与旧服务器完全相同的输出。
但这不是一个正确的解决方案,而是一个问题的缓解,最终的解决方案是将数据库更改为 UTF8,以避免出现更多问题。这就是我们最终要做的。
谢谢@jsagrera 我想将您的答案标记为正确答案。我是这个平台的新手,所以我不太了解它是如何工作的。如果您 post 将您的评论作为答案,我很乐意对其投赞成票,并尽可能将其标记为正确。
将用 c# 编写的旧应用程序(>15 岁)迁移到新的 Windows 服务器后,我们遇到了一个奇怪的问题。 该应用程序使用 OleDB 连接到 Informix 数据库。该数据库有一个 table 包含多种语言的文本。 Windows 2003 服务器中的应用程序 运行 工作正常,但是在新的 Windows 2016 中它会引发错误: "The data value could not be converted for reasons other than sign mismatch or data overflow. For example, the data was corrupted in the data store but the row was still retrievable."
经过一些调查,我们发现问题出在一个包含一些 unicode 字符的字符串中。
这是产生问题的部分文字(仅部分文字说明问题:
“17”-Leichtmetallräder ...... Ziffern - Schaltknauf
这是德语文本,看起来不错,问题实际上出在“-”上。查看十六进制的db记录,第一个“-”编码为“3F”,但是第二个破折号编码为"C296",对应于U+0096(unicode中的破折号)
DB的设置为en_US.819(对应ISO-8859-1,支持所有需要支持的语言)。
现在,问题是当 运行 Windows 2003 中的程序时,结果正确地写入文件中,如:
“17”-Leichtmetallräder ...... Ziffern - Schaltknauf
但是在 Windows 2016 年出现了上述异常,但没有写入任何内容。
我进行了一些代码更改,我做的第一件事是为 Odbc 连接更改 OleDB,异常消失了,但是输出中的文本不正确:
“17”-Leichtmetallräder ...... Ziffern?沙尔特克瑙夫
注意使用 odbc 连接的相同代码如何无法理解 unicode 破折号。
这是适用于 Windows 2003 的 OleDB 代码:
OleDbConnection ConnOleDbIDD = new OleDbConnection("Provider=Ifxoledbc.2;Data Source=db;INFORMIXSERVER=localhost;IFMX_UNDOC_B168163=1;"); string sConnectTemplateDB = "Data Source=SQLServerDB;Initial Catalog=DB1; Connect Timeout = 28800; Integrated Security=True"; ConnOleDbIDD.Open(); sExportSQL = "SELECT * From MyTable"; OleDbCommand cmdIDD = new OleDbCommand(sExportSQL, ConnOleDbIDD); cmdIDD.CommandTimeout = 28800; SqlDataAdapter da; ConnSchemaIDD = new SqlConnection (sConnectTemplateDB); ConnSchemaIDD.Open(); SqlCommand cmdSQLServerTemplate = new SqlCommand(sExportSQL.Replace("TRIM","LTRIM"), ConnSchemaIDD); cmdSQLServerTemplate.CommandTimeout = 28800; da = new SqlDataAdapter(cmdSQLServerTemplate); OleDbDataReader dr; DataSet ds = new DataSet(); da.MissingSchemaAction = MissingSchemaAction.AddWithKey; da.Fill(ds, sSourceTable); DataTable dt = ds.Tables[sSourceTable]; dr = cmdIDD.ExecuteReader() iEnCodingFrom = 1252; iEnCodingTo = 1252; while (dr.Read()) { sValue = ""; sCurrentValue = ""; bDelimiterPosition = false; foreach (DataColumn cCol in dt.Columns) { object oval = dr.GetValue(dr.GetOrdinal(cCol.ColumnName)); string 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(); } else { sValue = sValue + sCurrentValue.Trim(); } bDelimiterPosition = true; } w.WriteLine(sValue); w.Flush(); } dr.Close();
假设此示例 "Mytable" 有 2 列,第一列是整数 ID,第二列是 char(3100)。
如您所见,代码做了一些奇怪的事情,例如从 SQLServer 数据库中 table 的模式获取列描述,以及将 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 中也失败)而无需更改代码。
如有任何帮助,我们将不胜感激。
谢谢
感谢@LuísMarques 和@jsagrera 这正是我正在寻找的解释,所以现在我可以理解问题所在了。在文章中说:
"Since CSDK version 2.80, the ODBC driver is Unicode enabled, this means all the data the driver handles has to be in Unicode format. This means that a extra conversion has to be done"。
老服务器的csdk版本是2.71。新服务器的版本是4.10.
现在,由于这个原因,"UNDOC" 在那里,数据库是使用 en_us.819 创建的,但是我的客户端应用程序的 "undoc" 变量忽略了它,它假设数据进入 CP1252 并在 CP1252 中打印出来,程序无需任何内部转换即可运行。
但是数据库中的数据无论如何都已损坏。升级驱动程序后,进行内部转换会产生错误。
我仍然可以解决它,我不在 ODBC 连接中使用 "UNDOC",然后我从数据库中获取字节流并在我的数据库中进行从 8859-1 到 CP1252 的转换C# 代码。这样我得到与旧服务器完全相同的输出。
但这不是一个正确的解决方案,而是一个问题的缓解,最终的解决方案是将数据库更改为 UTF8,以避免出现更多问题。这就是我们最终要做的。
谢谢@jsagrera 我想将您的答案标记为正确答案。我是这个平台的新手,所以我不太了解它是如何工作的。如果您 post 将您的评论作为答案,我很乐意对其投赞成票,并尽可能将其标记为正确。