52

我正在尝试插入 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,以及当encoding您尝试插入的 xml 规范的属性中的 utf-16 时:

当您要添加时,请utf-8向 SQL 命令添加参数,如下所示:

 sqlcmd.Parameters.Add("ParamName", SqlDbType.VarChar).Value = xmlValueToAdd;

如果您尝试encoding=utf-16在上一行中添加 xmlValueToAdd ,则会在插入时产生错误。此外,这VarChar意味着无法识别国家字符(它们变成问号)。

要将 utf-16 添加到 db,请使用SqlDbType.NVarCharSqlDbType.Xml在前面的示例中,或者根本不指定类型:

 sqlcmd.Parameters.Add(new SqlParameter("ParamName", xmlValueToAdd));
4

8 回答 8

36

这个问题几乎与其他两个问题重复,而且令人惊讶的是 - 虽然这是最近的问题 - 我相信它缺少最佳答案。

重复的,我认为是他们最好的答案,是:

最后,声明或使用什么编码并不重要,只要XmlReader可以在应用程序服务器中本地解析它即可。

正如在 SQL server 中的 XML 类型列中读取 ADO.net 中 XML 的最有效方法中所确认的那样?, SQL Server 以一种有效的二进制格式存储 XML。通过使用SqlXml该类,ADO.net 可以以这种二进制格式与 SQL Server 进行通信,并且不需要数据库服务器对 XML 进行任何序列化或反序列化。这对于跨网络的传输也应该更有效。

通过使用SqlXml,XML 将被预先解析发送到数据库,然后数据库不需要知道任何关于字符编码的信息 - 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();
            }
        }
    }
}

请注意,我不会将最后一个(未注释的)示例视为“生产就绪”,而是保持原样以简洁易读。如果做得正确,应该在语句中初始化theStringReader和 created ,以确保在完成时调用它们的方法。XmlReaderusingClose()

据我所见,使用 XML 列时,XML 声明永远不会持久化。例如,即使不使用 .NET 并且仅使用此直接 SQL 插入语句,XML 声明也不会与 XML 一起保存到数据库中:

Insert Into TestData(Xml) Values ('<?xml version="1.0" encoding="UTF-8"?><Test/>');

现在就OP的问题而言,要序列化的对象仍然需要从对象转换为XML结构MyMessage,并且XmlSerializer仍然需要这样做。然而,在最坏的情况下,消息不是序列化为字符串,而是可以序列化为XmlDocument- 然后可以通过SqlXml新的 - 传递给XmlNodeReader- 避免对字符串的反序列化/序列化之旅。(有关详细信息和示例,请参阅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 开发和测试的。

请不要浪费通过额外的转换(反序列化和序列化 - 到 DOM、字符串或其他)运行 XML,如此处和其他地方的其他答案所示。

于 2012-01-25T05:18:30.280 回答
22

尽管 .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"?>
于 2010-09-21T13:55:16.497 回答
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();
于 2013-01-29T09:29:41.790 回答
12

我花了很长时间才重新解决这个问题。

我在INSERTSQL Server 中做了一个语句,如下所示:

UPDATE Customers 
SET data = '<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';

这给出了错误:

Msg 9402,Level 16,State 1,Line 2
XML 解析:line 1,character 39,无法切换编码

真正非常简单的解决方法是:

UPDATE Customers 
SET data = N'<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';

不同之处在于 Unicode 字符串的前缀为N

N '<?xml version="1.0" encoding="utf-16"?>Teno</MyMessage>'

在前一种情况下,无前缀的字符串被假定为 varchar(例如 Windows-1252 代码页)。当它遇到encoding="utf-16"字符串内部时,就会发生冲突(这是正确的,因为字符串不是utf-16)。

解决方法是将字符串作为nvarchar(即 UTF-16)传递给 SQL 服务器:

N '<?xml version="1.0" encoding="utf-16"?>'

这样,字符串就是UTF-16,它与 XML 所说的 utf-16 编码相匹配。地毯与窗帘相配,可以这么说。

于 2016-02-22T16:48:23.610 回答
6

@ziesemer 的答案(上图)是该问题的唯一完全正确答案以及该问题的链接副本。但是,它仍然可以使用更多的解释和澄清。将此视为@ziesemer 答案的扩展。


即使他们产生了预期的结果,这个问题的大多数答案(包括重复的问题)都是令人费解的,并且要经过许多不必要的步骤。这里的主要问题是总体上缺乏对XML数据类型在 SQL Server 中的实际工作方式的理解(这并不奇怪,因为它没有很好的文档记录)。XML类型:

  1. 是一种高度优化的(用于存储)类型,可将传入的 XML 转换为二进制格式(在msdn站点的某处记录)。优化包括:
    1. 如果元素或属性用类型信息标记(这可能需要指定 XML 模式集合),则将数字和日期从字符串(就像它们在 XML 中一样)转换为二进制表示。意思是,数字“1234567”存储为 4 字节的“int”,而不是 7 位的 14 字节 UTF-16 字符串。
    2. 元素和属性名称存储在字典中并给出数字 ID。该数字 ID 用于 XML 树结构。意思是," <ElementName>...</ElementName>" 以字符串形式占用 27 个字符(即 54 个字节),但存储在XML类型中时只占用 11 个字符(即 22 个字节)。那是它的一个实例。多个实例占用 54 个字节的额外倍数。但是在 XML 类型中,每个实例只占用那个数字 ID 的空间,很可能是一个 4 字节的 int。
  2. 始终将字符串存储为 UTF-16 Little Endian 。这很可能是不存储 XML 声明的原因:它完全没有必要,因为它总是相同的,因为“编码”属性永远不会改变。
  3. 没有 XML 声明假定编码是 UTF-16,而不是UTF-8。
  4. 可以传入 8 位/非 UTF-16 数据。在这种情况下,您需要确保字符串不是NVARCHAR字符串(即不以大写字母“N”为前缀,不声明为NVARCHARwhen处理 T-SQL 变量,而不是SqlDbType.NVarChar在 .NET 中声明)。并且,您需要确保您确实拥有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”。

  5. 如果输入字符串是,则可以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 个字节显示,ùìåí因为当前 DB 的默认排序规则是Latin1_General_100_CS_AS_SC.

于 2018-12-04T19:33:28.240 回答
5

字符串在 .NET 中始终是 UTF-16,因此只要您留在托管应用程序中,您就不必关心它是哪种编码。

问题更可能出现在您与 SQL 服务器对话的地方。您的问题没有显示该代码,因此很难确定确切的错误。我的建议是您检查是否可以在该代码上设置一个属性或属性,以指定发送到服务器的数据的编码。

于 2010-09-21T13:40:14.837 回答
1

您正在序列化为字符串而不是字节数组,因此,此时尚未发生任何编码。

“messageToLog”的开头是什么样的?XML 是否指定了随后被证明是错误的编码(例如 utf-8)?

编辑

根据您的进一步信息,听起来该字符串在传递给数据库时会自动转换为 utf-8,但是由于 XML 声明说它是 utf-16,因此数据库阻塞了。

在这种情况下,您不需要序列化为 utf-8。您需要使用 XML 中省略的“encoding=”进行序列化。XmlFragmentWriter(不是 .Net 的标准部分,谷歌它)可以让你做到这一点。

于 2010-09-21T13:43:37.680 回答
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();
于 2010-09-21T13:42:43.300 回答