将XML插入SQL Server时如何解决"无法切换编码"错误

vel*_*koz 43 .net xml sql-server utf-8 utf-16

我正在尝试插入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();
Run Code Online (Sandbox Code Playgroud)

如何将对象序列化为UTF-8字符串?

编辑:好的,抱歉混淆 - 字符串需要是UTF-8.你是对的 - 默认情况下它是UTF-16,如果我尝试以UTF-8插入它就会通过.所以问题是如何序列化为UTF-8.

这会在尝试插入SQL Server时导致错误:

    <?xml version="1.0" encoding="utf-16"?>
    <MyMessage>Teno</MyMessage>
Run Code Online (Sandbox Code Playgroud)

这不是:

    <?xml version="1.0" encoding="utf-8"?>
    <MyMessage>Teno</MyMessage>
Run Code Online (Sandbox Code Playgroud)

更新

我想出当SQL Server 2008的Xml列类型需要utf-8时,以及当encoding你尝试插入xml规范的属性中的utf-16时:

如果要添加utf-8,请将参数添加到SQL命令,如下所示:

 sqlcmd.Parameters.Add("ParamName", SqlDbType.VarChar).Value = xmlValueToAdd;
Run Code Online (Sandbox Code Playgroud)

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

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

 sqlcmd.Parameters.Add(new SqlParameter("ParamName", xmlValueToAdd));
Run Code Online (Sandbox Code Playgroud)

zie*_*mer 34

这个问题几乎与其他2个问题重复,令人惊讶的是 - 虽然这个问题是最新的 - 但我相信它缺少最佳答案.

重复,以及我认为最佳答案,是:

最后,只要XmlReader可以在应用程序服务器中本地解析它,无论声明或使用什么编码都无关紧要.

正如在SQL Server中从XML类型列中读取ADO.net中的XML的最有效方式所证实的那样,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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我不会将最后一个(未注释的)示例视为"生产就绪",而是将其保留为简洁易读.如果正确完成,则应在语句中初始化the StringReader和created,以确保在完成时调用其方法.XmlReaderusingClose()

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

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

现在就OP的问题而言,要序列化的对象仍然需要从MyMessage对象转换为XML结构,并且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来消除浪费,如此处和其他地方的其他答案所示.

  • 不仅可以很好地处理编码切换的问题,而且还可以提高性能。在某种程度上,我们在不使用它时仍然会出现错误是件好事,这提醒我们应该采取不同的做法。很好,你用更多的例子重复了这个答案:-) (2认同)
  • +1,请参阅我的答案,这是这个好答案的附录:/sf/answers/3753412981/ (2认同)

小智 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;
}
Run Code Online (Sandbox Code Playgroud)

通过将编码设置为Encoding.Unicode,不仅字符串将是,UTF-16但您还应该获取xml字符串UTF-16.

<?xml version="1.0" encoding="utf-16"?>
Run Code Online (Sandbox Code Playgroud)


bat*_*wad 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();
Run Code Online (Sandbox Code Playgroud)


Ian*_*oyd 7

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

我正在对INSERTSQL Server 做一个声明,如:

UPDATE Customers 
SET data = '<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';
Run Code Online (Sandbox Code Playgroud)

这给出了错误:

消息9402,级别16,状态1,行2
XML解析:第1行,字符39,无法切换编码

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

UPDATE Customers 
SET data = N'<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';
Run Code Online (Sandbox Code Playgroud)

区别在于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编码.可以这么说,地毯与窗帘相配.


Sol*_*zky 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!
    
    Run Code Online (Sandbox Code Playgroud)

    如您所见,当输入字符串为 时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="????" />     ùìåí
    */
    
    Run Code Online (Sandbox Code Playgroud)

    第一个示例为Smiling Face with Sunglasses指定了 4 字节的 UTF-8 序列,并且它被正确转换。
    第二个示例使用 4 个字节来表示组成单词“Shalom”的 4 个希伯来字母,它被正确转换,并正确显示,因为第一个“F9”字节?是右侧的字符(因为希伯来语是从右到左的语言)。然而ùìåí,由于当前 DB 的默认排序规则是Latin1_General_100_CS_AS_SC.


Isa*_*avo 5

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

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

  • @veljkoz-SQL Server无法接受UTF-8编码的XML值。对我来说,解决方案是剥离XML声明,因为它始终不会与XML数据一起存储。请参阅http://stackoverflow.com/a/9002485/895218。 (2认同)