我正在尝试插入XML列(SQL SERVER 2008 R2),但服务器抱怨:
System.Data.SqlClient.SqlException(0x80131904):
XML解析:第1行,第39个字符,无法切换编码
我发现XML列必须是UTF-16才能使插入成功。
我正在使用的代码是:
XmlSerializer serializer = new XmlSerializer(typeof(MyMessage));
StringWriter str = new StringWriter();
serializer.Serialize(str, message);
string messageToLog = str.ToString();
如何将对象序列化为UTF-8字符串?
编辑:好的,抱歉混淆 - 字符串需要是UTF-8。你是对的 - 默认情况下它是UTF-16,如果我尝试以UTF-8插入它就会通过。所以问题是如何序列化为UTF-8。
示例
这会在尝试插入SQL Server时导致错误:
<?xml version="1.0" encoding="utf-16"?>
<MyMessage>Teno</MyMessage>
这不是:
<?xml version="1.0" encoding="utf-8"?>
<MyMessage>Teno</MyMessage>
更新
我想出SQL Server 2008的Xml
列类型需要utf-8,以及当你尝试插入的xml规范的encoding
属性中的utf-16时:
如果要添加utf-8
,请将参数添加到SQL命令,如下所示:
sqlcmd.Parameters.Add("ParamName", SqlDbType.VarChar).Value = xmlValueToAdd;
如果您尝试在前一行添加带有encoding=utf-16
的xmlValueToAdd,则会在插入中产生错误。此外,VarChar
表示不识别国家字符(它们最终显示为问号)。
要将utf-16添加到db,请在前面的示例中使用SqlDbType.NVarChar
或SqlDbType.Xml
,或者根本不指定类型:
sqlcmd.Parameters.Add(new SqlParameter("ParamName", xmlValueToAdd));
答案 0 :(得分:34)
这个问题几乎与其他2个问题重复,令人惊讶的是 - 虽然这个问题是最新的 - 但我相信它缺少最佳答案。
重复,以及我认为最佳答案,是:
最后,只要XmlReader
可以在应用程序服务器中本地解析它,无论声明或使用什么编码都无关紧要。
正如Most efficient way to read XML in ADO.net from XML type column in SQL server?中所确认的那样,SQL Server以高效的二进制格式存储XML。通过使用SqlXml
类,ADO.net可以以此二进制格式与SQL Server通信,而不需要数据库服务器对XML进行任何序列化或反序列化。这对于通过网络传输也应该更有效。
通过使用SqlXml
,XML将被预先解析到数据库,然后DB不需要知道有关字符编码的任何信息 - UTF-16或其他。特别要注意的是,XML声明甚至不会与数据库中的数据一起保留,无论使用哪种方法插入它。
请参阅上面链接的答案,了解与此非常相似的方法,但这个例子是我的:
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using System.Xml;
static class XmlDemo {
static void Main(string[] args) {
using(SqlConnection conn = new SqlConnection()) {
conn.ConnectionString = "...";
conn.Open();
using(SqlCommand cmd = new SqlCommand("Insert Into TestData(Xml) Values (@Xml)", conn)) {
cmd.Parameters.Add(new SqlParameter("@Xml", SqlDbType.Xml) {
// Works.
// Value = "<Test/>"
// Works. XML Declaration is not persisted!
// Value = "<?xml version=\"1.0\"?><Test/>"
// Works. XML Declaration is not persisted!
// Value = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><Test/>"
// Error ("unable to switch the encoding" SqlException).
// Value = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Test/>"
// Works. XML Declaration is not persisted!
Value = new SqlXml(XmlReader.Create(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Test/>")))
});
cmd.ExecuteNonQuery();
}
}
}
}
请注意,我不会将最后一个(未注释的)示例视为“生产就绪”,而是将其保留为简洁易读。如果操作正确,StringReader
和已创建的XmlReader
都应在using
语句中初始化,以确保在完成时调用其Close()
方法。
从我所看到的,使用XML列时,XML声明永远不会持久化。例如,即使不使用.NET并仅使用此直接SQL插入语句,XML声明也不会使用XML保存到数据库中:
Insert Into TestData(Xml) Values ('<?xml version="1.0" encoding="UTF-8"?><Test/>');
现在就OP的问题而言,要序列化的对象仍然需要从MyMessage
对象转换为XML结构,并且仍然需要XmlSerializer
。但是,在最坏的情况下,而不是序列化为String,而是可以将消息序列化为XmlDocument
- 然后可以通过新的XmlNodeReader
将其传递给SqlXml
- 避免使用序列化/序列化跳转到字符串。 (有关详细信息和示例,请参阅http://blogs.msdn.com/b/jongallant/archive/2007/01/30/how-to-convert-xmldocument-to-xmlreader-for-sqlxml-data-type.aspx。)
这里的所有内容都是针对.NET 4.0和SQL Server 2008 R2进行开发和测试的。
请不要浪费,方法是通过额外的转换运行XML(去反序列化和序列化 - 到DOM,字符串或其他方式),如此处和其他地方的其他答案所示。
答案 1 :(得分:21)
虽然.net字符串始终为UTF-16
,但您需要使用UTF-16
编码序列化对象。
那应该是这样的:
public static string ToString(object source, Type type, Encoding encoding)
{
// The string to hold the object content
String content;
// Create a memoryStream into which the data can be written and readed
using (var stream = new MemoryStream())
{
// Create the xml serializer, the serializer needs to know the type
// of the object that will be serialized
var xmlSerializer = new XmlSerializer(type);
// Create a XmlTextWriter to write the xml object source, we are going
// to define the encoding in the constructor
using (var writer = new XmlTextWriter(stream, encoding))
{
// Save the state of the object into the stream
xmlSerializer.Serialize(writer, source);
// Flush the stream
writer.Flush();
// Read the stream into a string
using (var reader = new StreamReader(stream, encoding))
{
// Set the stream position to the begin
stream.Position = 0;
// Read the stream into a string
content = reader.ReadToEnd();
}
}
}
// Return the xml string with the object content
return content;
}
通过将编码设置为Encoding.Unicode,不仅字符串为UTF-16
,而且还应将xml字符串设为UTF-16
。
<?xml version="1.0" encoding="utf-16"?>
答案 2 :(得分:12)
告诉序列化程序不输出XML声明是不是最简单的解决方案? .NET和SQL应该对它们进行排序。
XmlSerializer serializer = new XmlSerializer(typeof(MyMessage));
StringWriter str = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(str, new XmlWriterSettings { OmitXmlDeclaration = true }))
{
serializer.Serialize(writer, message);
}
string messageToLog = str.ToString();
答案 3 :(得分:7)
我花了很长时间才重新解决这个问题。
我正在对SQL Server执行INSERT
语句,如:
UPDATE Customers
SET data = '<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';
这会产生错误:
Msg 9402,Level 16,State 1,Line 2
XML解析:第1行,第39个字符,无法切换编码
真正非常简单的解决办法是:
UPDATE Customers
SET data = N'<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';
区别在于Unicode字符串前缀为 N
:
N &#39;&lt;?xml version =&#34; 1.0&#34;编码=&#34; UTF-16&#34;&GT;特诺&LT; / MyMessage&GT;&#39;
在前一种情况下,假定未加前缀的字符串是varchar(例如,Windows-1252代码页)。当它遇到字符串中的encoding="utf-16"
时,就会发生冲突(正确的是,因为字符串不是 utf-16)。
修复方法是将字符串作为 nvarchar (即UTF-16)传递给SQL服务器:
N &#39;&lt;?xml version =&#34; 1.0&#34;编码=&#34; UTF-16&#34;&GT;&#39;
这样字符串是 UTF-16,它匹配XML所说的utf-16编码。可以这么说,地毯与窗帘相配。
答案 4 :(得分:5)
在.NET中字符串总是UTF-16,因此只要您留在托管应用程序中,就不必关心它是哪种编码。
问题更可能发生在与SQL服务器通信的地方。您的问题没有显示该代码,因此很难确定错误。我的建议是检查是否有可以在该代码上设置的属性或属性,用于指定发送到服务器的数据的编码。
答案 5 :(得分:4)
@ziesemer's answer(上方)是对该问题以及该问题的链接重复项的唯一完全正确的答案。但是,它仍然可以使用更多的解释和一些说明。将此视为@ziesemer答案的扩展。
即使产生期望的结果,该问题的大多数答案(包括重复的问题)也会令人费解,并经过许多不必要的步骤。这里的主要问题是对XML
数据类型在SQL Server中的实际工作方式的总体了解不足(鉴于记录不足,不足为奇)。 XML
类型:
msdn
网站的某个位置进行了记录)。优化包括:
<ElementName>...</ElementName>
”以字符串形式占用27个字符(即54个字节),但以XML
类型存储时仅占11个字符(即22个字节)。那是它的一个实例。多个实例占用了54个字节的其他倍数。但是在XML类型中,每个实例仅占用该数字ID的空间,很可能是4字节的整数。可以传入8位/非UTF-16数据。在这种情况下,您需要确保该字符串不是NVARCHAR
字符串(<例如,在文字上没有前缀大写的“ N”,在处理T-SQL变量时未声明为NVARCHAR
,在.NET中未声明为SqlDbType.NVarChar
。并且,您需要确保 do 具有XML
声明,并指定正确的编码。
PRINT 'VARCHAR / UTF-8:';
DECLARE @XML_VC_8 XML;
SET @XML_VC_8 = '<?xml version="1.0" encoding="utf-8"?><test/>';
PRINT 'Success!'
-- Success!
GO
PRINT '';
PRINT 'NVARCHAR / UTF-8:';
DECLARE @XML_NVC_8 XML;
SET @XML_NVC_8 = N'<?xml version="1.0" encoding="utf-8"?><test/>';
PRINT 'Success!'
/*
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 38, unable to switch the encoding
*/
GO
PRINT '';
PRINT 'VARCHAR / UTF-16:';
DECLARE @XML_VC_16 XML;
SET @XML_VC_16 = '<?xml version="1.0" encoding="utf-16"?><test/>';
PRINT 'Success!'
/*
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 38, unable to switch the encoding
*/
GO
PRINT '';
PRINT 'NVARCHAR / UTF-16:';
DECLARE @XML_NVC_16 XML;
SET @XML_NVC_16 = N'<?xml version="1.0" encoding="utf-16"?><test/>';
PRINT 'Success!'
-- Success!
如您所见,当输入字符串为NVARCHAR
时,可以包含XML声明 ,但它必须为“ UTF-16”。
当输入字符串为VARCHAR
时,可以包含XML声明 ,但不能为“ UTF-16”。但是,它可以是任何有效的8位编码,在这种情况下,该编码的字节将转换为UTF-16,如下所示:
DECLARE @XML XML;
SET @XML = '<?xml version="1.0" encoding="utf-8"?><test attr="'
+ CHAR(0xF0) + CHAR(0x9F) + CHAR(0x98) + CHAR(0x8E) + '"/>';
SELECT @XML;
-- <test attr="" />
SET @XML = '<?xml version="1.0" encoding="Windows-1255"?><test attr="'
+ CONVERT(VARCHAR(10), 0xF9ECE5ED) + '"/>';
SELECT @XML AS [XML from Windows-1255],
CONVERT(VARCHAR(10), 0xF9ECE5ED) AS [Latin1_General / Windows-1252];
/*
XML from Windows-1255 Latin1_General / Windows-1252
<test attr="שלום" /> ùìåí
*/
第一个示例为Smiling Face with Sunglasses指定了4字节的UTF-8序列,并且可以正确转换。
第二个示例使用4个字节来表示组成单词“ Shalom”的4个希伯来字母,该单词正确转换并正确显示,前提是第一个字符“ F9”是ש
字符,即在单词的右侧(因为希伯来语是从右到左的语言)。但是,由于当前数据库的默认归类为ùìåí
,直接选择时,这些相同的4个字节将显示为Latin1_General_100_CS_AS_SC
。
答案 6 :(得分:1)
您正在序列化为字符串而不是字节数组,因此,此时尚未发生任何编码。
“messageToLog”的开头是什么样的? XML是否指定了一个后来证明是错误的编码(例如utf-8)?
修改强>
根据您的进一步信息,听起来字符串在传递给数据库时会自动转换为utf-8,但数据库会因为XML声明显示为utf-16而窒息。
在这种情况下,您不需要序列化为utf-8。您需要使用XML中省略的“encoding =”进行序列化。 XmlFragmentWriter(不是.Net的标准部分,谷歌)可以让你这样做。
答案 7 :(得分:0)
xml序列化程序的默认编码应为UTF-16。只是为了确保你可以尝试 -
XmlSerializer serializer = new XmlSerializer(typeof(YourObject));
// create a MemoryStream here, we are just working
// exclusively in memory
System.IO.Stream stream = new System.IO.MemoryStream();
// The XmlTextWriter takes a stream and encoding
// as one of its constructors
System.Xml.XmlTextWriter xtWriter = new System.Xml.XmlTextWriter(stream, Encoding.UTF16);
serializer.Serialize(xtWriter, yourObjectInstance);
xtWriter.Flush();