<TL; DR>问题实际上很简单:您没有将声明的编码(在XML声明中)与输入参数的数据类型匹配。如果您是手动添加<?xml version="1.0" encoding="utf-8"?><test/>
到字符串中,则声明的SqlParameter
类型为SqlDbType.Xml
或SqlDbType.NVarChar
会给您“无法切换编码”错误。然后,当通过T-SQL手动插入时,由于将声明的编码切换为utf-16
,因此您显然插入了一个VARCHAR
字符串(不以大写字母“ N”作为前缀,因此是8位编码,例如UTF-8)。而不是NVARCHAR
字符串(以大写字母“ N”为前缀,因此为16位UTF-16 LE编码)。
该修复程序应该很简单:
- 在第一种情况下,添加声明时
encoding="utf-8"
:只需不添加XML声明。
- 在第二种情况下,添加声明时
encoding="utf-16"
:
- 根本不添加XML声明,或者
- 只需在输入参数类型中添加“ N”:
SqlDbType.NVarChar
而不是: -) SqlDbType.VarChar
(甚至可能切换到using SqlDbType.Xml
)
(详细回复如下)
这里的所有答案都过于复杂和不必要(无论克里斯汀和乔恩的答案分别为121和184否决)。他们可能会提供有效的代码,但没有一个人真正回答问题。问题是没有人真正地理解这个问题,而这个问题最终与SQL Server中XML数据类型的工作方式有关。这两个显然很聪明的人并不反对,但是这个问题与序列化到XML几乎没有关系。将XML数据保存到SQL Server中比在此隐含的要容易得多。
只要您遵循如何在SQL Server中创建XML数据的规则,如何生成XML都没有关系。在以下问题的答案中,我得到了更详尽的解释(包括工作示例代码,以说明以下要点):在将XML插入SQL Server时,如何解决“无法切换编码”错误,但是基础是:
- XML声明是可选的
- XML数据类型始终将字符串存储为UCS-2 / UTF-16 LE
- 如果您的XML是UCS-2 / UTF-16 LE,那么您:
- 以
NVARCHAR(MAX)
或XML
/ SqlDbType.NVarChar
(maxsize = -1)或形式传递数据SqlDbType.Xml
,或者如果使用字符串文字,则必须以大写字母“ N”作为前缀。
- 如果指定XML声明,则必须为“ UCS-2”或“ UTF-16”(此处无实际区别)
- 如果您的XML是8位编码的(例如“ UTF-8” /“ iso-8859-1” /“ Windows-1252”),则您:
- 如果编码与数据库默认排序规则指定的代码页不同,则需要指定XML声明
- 您必须以
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1)的形式传递数据,或者如果使用字符串文字,则该数据不得以大写字母“ N”作为前缀。
- 无论使用哪种8位编码,XML声明中注明的“编码”都必须与字节的实际编码匹配。
- 8位编码将通过XML数据类型转换为UTF-16 LE
有了上面的概述考虑点,并考虑到在.NET字符串总是 UTF-16 LE / UCS-2 LE(有编码的那些方面没有区别),我们可以回答您的问题:
为什么在以后需要字符串时不使用StringWriter序列化对象的原因呢?
不,您的StringWriter
代码看起来还不错(至少在我使用问题的第二个代码块进行的有限测试中,我没有发现任何问题)。
这样,将编码设置为UTF-16(在xml标记中)就行了吗?
无需提供XML声明。如果缺少该字符串,则如果将字符串作为NVARCHAR
(即SqlDbType.NVarChar
)或XML
(即SqlDbType.Xml
)传递给SQL Server,则假定编码为UTF-16 LE 。如果以VARCHAR
(即SqlDbType.VarChar
)传入,则假定编码为默认的8位代码页。如果您有任何非标准ASCII字符(即值128和更高),并且以传入VARCHAR
,那么您很可能会看到“?” 用于BMP字符和“ ??” SQL Server将把.NET中的UTF-16字符串转换为当前数据库代码页的8位字符串,然后再将其转换回UTF-16 / UCS-2。但是您不应该得到任何错误。
另一方面,如果确实指定XML声明,则必须使用匹配的8位或16位数据类型传递到SQL Server。因此,如果您有声明说明编码为UCS-2或UTF-16,则必须以SqlDbType.NVarChar
或形式传递SqlDbType.Xml
。或者,如果你有一个声明,表示编码是8位的选项之一(即UTF-8
,Windows-1252
,iso-8859-1
等等),那么你必须在为合格SqlDbType.VarChar
。无法将声明的编码与正确的8位或16位SQL Server数据类型匹配,将导致您收到“无法切换编码”错误。
例如,使用StringWriter
基于您的序列化代码,我只打印了XML的结果字符串,并将其用于SSMS。如下所示,其中包含XML声明(因为StringWriter
没有OmitXmlDeclaration
类似的选项XmlWriter
),只要将字符串作为正确的SQL Server数据类型传递,就不会出现问题:
-- Upper-case "N" prefix == NVARCHAR, hence no error:
DECLARE @Xml XML = N'<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
SELECT @Xml;
-- <string>Test ሴ😸</string>
如您所见,鉴于ሴ
BMP代码点U + 1234和😸
补充字符代码点U + 1F638 ,它甚至可以处理超出标准ASCII的字符。但是,以下内容:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
导致以下错误:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
因此,除了所有这些解释之外,您原来的问题的完整解决方案是:
您显然是将字符串传递为SqlDbType.VarChar
。切换到SqlDbType.NVarChar
,它将无需删除XML声明的额外步骤即可工作。这比保留SqlDbType.VarChar
和删除XML声明要好,因为当XML包含非标准ASCII字符时,此解决方案将防止数据丢失。例如:
-- No upper-case "N" prefix on the string literal == VARCHAR, and no XML declaration:
DECLARE @Xml2 XML = '<string>Test ሴ😸</string>';
SELECT @Xml2;
-- <string>Test ???</string>
如您所见,这次没有错误,但是现在有数据丢失🙀。