代码之家  ›  专栏  ›  技术社区  ›  StampedeXV

使用StringWriter进行XML序列化

  •  87
  • StampedeXV  · 技术社区  · 16 年前

    我目前正在寻找一种简单的序列化对象的方法(在C#3中)。

    我在谷歌上搜索了一些例子,得出了如下结论:

    MemoryStream memoryStream = new MemoryStream ( );
    XmlSerializer xs = new XmlSerializer ( typeof ( MyObject) );
    XmlTextWriter xmlTextWriter = new XmlTextWriter ( memoryStream, Encoding.UTF8 );
    xs.Serialize ( xmlTextWriter, myObject);
    string result = Encoding.UTF8.GetString(memoryStream .ToArray());
    

    看完这个 question 我问自己,为什么不使用StringWriter?看起来容易多了。

    XmlSerializer ser = new XmlSerializer(typeof(MyObject));
    StringWriter writer = new StringWriter();
    ser.Serialize(writer, myObject);
    serializedValue = writer.ToString();
    

    另一个问题是,第一个示例生成的XML我不能直接写入SQLServer2005DB的XML列中。

    第一个问题是:当我以后需要将对象作为字符串时,是否有理由不使用StringWriter序列化对象?我在谷歌搜索时从未发现使用StringWriter的结果。

    当然,第二个问题是:如果您不应该使用StringWriter(出于任何原因),那么哪种方法是好的、正确的?


    补充:

    正如两个答案中已经提到的,我将进一步讨论XML到DB的问题。

    XML解析:第1行,字符38, 无法切换编码

    <?xml version="1.0" encoding="utf-8"?><test/>
    

    之后,我尝试使用encoding=“utf-16”手动插入(只是将插入写入…),但也失败了。

    问题:我真的不明白为什么。

    在Christian Hayter:通过这些测试,我不确定是否必须使用utf-16来写入数据库。那么,将编码设置为UTF-16(在xml标记中)不起作用吗?

    6 回复  |  直到 7 年前
        1
  •  224
  •   Jon Skeet    9 年前

    有一个问题 StringWriter 这是默认的吗 it doesn't let you set the encoding which it advertises

    public sealed class StringWriterWithEncoding : StringWriter
    {
        public override Encoding Encoding { get; }
    
        public StringWriterWithEncoding (Encoding encoding)
        {
            Encoding = encoding;
        }    
    }
    

    或者如果您只需要UTF-8(这是我经常需要的):

    public sealed class Utf8StringWriter : StringWriter
    {
        public override Encoding Encoding => Encoding.UTF8;
    }
    

    至于为什么您不能将XML保存到数据库中,如果您希望我们能够诊断/修复它,您必须向我们提供有关您尝试时发生的情况的更多详细信息。

        2
  •  126
  •   lordnik22    8 年前

    将XML文档序列化为.NET字符串时,编码必须设置为UTF-16。字符串在内部存储为UTF-16,因此这是唯一有意义的编码。如果您想以不同的编码存储数据,可以使用字节数组。

    SQL Server的工作原理与此类似;传递到 xml 列必须编码为UTF-16。SQL Server将拒绝XML声明未指定UTF-16的任何字符串。如果XML声明不存在,则XML标准要求它默认为UTF-8,因此SQL Server也将拒绝该声明。

    记住这一点,这里有一些实用的转换方法。

    public static string Serialize<T>(T value) {
    
        if(value == null) {
            return null;
        }
    
        XmlSerializer serializer = new XmlSerializer(typeof(T));
    
        XmlWriterSettings settings = new XmlWriterSettings()
        {
            Encoding = new UnicodeEncoding(false, false), // no BOM in a .NET string
            Indent = false,
            OmitXmlDeclaration = false
        };
    
        using(StringWriter textWriter = new StringWriter()) {
            using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
                serializer.Serialize(xmlWriter, value);
            }
            return textWriter.ToString();
        }
    }
    
    public static T Deserialize<T>(string xml) {
    
        if(string.IsNullOrEmpty(xml)) {
            return default(T);
        }
    
        XmlSerializer serializer = new XmlSerializer(typeof(T));
    
        XmlReaderSettings settings = new XmlReaderSettings();
        // No settings need modifying here
    
        using(StringReader textReader = new StringReader(xml)) {
            using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
                return (T) serializer.Deserialize(xmlReader);
            }
        }
    }
    
        3
  •  20
  •   John Saunders    16 年前

    XmlTextWriter ,自.NET 2.0起已弃用。 XmlWriter.Create

    以下是将对象序列化为XML列的示例:

    public void SerializeToXmlColumn(object obj)
    {
        using (var outputStream = new MemoryStream())
        {
            using (var writer = XmlWriter.Create(outputStream))
            {
                var serializer = new XmlSerializer(obj.GetType());
                serializer.Serialize(writer, obj);
            }
    
            outputStream.Position = 0;
            using (var conn = new SqlConnection(Settings.Default.ConnectionString))
            {
                conn.Open();
    
                const string INSERT_COMMAND = @"INSERT INTO XmlStore (Data) VALUES (@Data)";
                using (var cmd = new SqlCommand(INSERT_COMMAND, conn))
                {
                    using (var reader = XmlReader.Create(outputStream))
                    {
                        var xml = new SqlXml(reader);
    
                        cmd.Parameters.Clear();
                        cmd.Parameters.AddWithValue("@Data", xml);
                        cmd.ExecuteNonQuery();
                    }
                }
            }
        }
    }
    
        4
  •  1
  •   splash    13 年前
    public static T DeserializeFromXml<T>(string xml)
    {
        T result;
        XmlSerializerFactory serializerFactory = new XmlSerializerFactory();
        XmlSerializer serializer =serializerFactory.CreateSerializer(typeof(T));
    
        using (StringReader sr3 = new StringReader(xml))
        {
            XmlReaderSettings settings = new XmlReaderSettings()
            {
                CheckCharacters = false // default value is true;
            };
    
            using (XmlReader xr3 = XmlTextReader.Create(sr3, settings))
            {
                result = (T)serializer.Deserialize(xr3);
            }
        }
    
        return result;
    }
    
        5
  •  1
  •   Solomon Rutzky    7 年前

    <TL;DR> <?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编码)。

    修复应该简单到:

    1. 在第一种情况下,在添加声明时 encoding="utf-8" :只需不添加XML声明。
    2. 在第二种情况下,在添加声明时 encoding="utf-16" :要么
      1. 只需在输入参数类型中添加一个“N”: 而不是 SqlDbType.VarChar :-)(甚至可能切换到使用 Xml

    (详细答复如下)


    只要您遵循如何在SQLServer中创建XML数据的规则,XML是如何生成的其实并不重要。在回答这个问题时,我有一个更全面的解释(包括用于说明以下要点的工作示例代码): How to solve “unable to switch the encoding” error when inserting XML into SQL Server ,但最基本的是:

    1. XML声明是可选的
    2. XML数据类型总是将字符串存储为UCS-2/UTF-16 LE
    3. 如果您的XML是UCS-2/UTF-16 LE,那么您:
      1. 将数据作为 NVARCHAR(MAX) XML SqlDbType.NVarChar (maxsize=-1)或 Xml
    4. 如果您的XML是8位编码的(例如“UTF-8”/“iso-8859-1”/“Windows-1252”),那么您:
      1. 您必须将数据作为 VARCHAR(MAX) SqlDbType.VarChar (maxsize=-1),或者如果使用字符串文字,则它必须 以大写字母“N”作为前缀。
      2. 8位编码将通过XML数据类型转换为UTF-16 LE

    总是

    当我以后需要将对象序列化为字符串时,是否有理由不使用StringWriter来序列化对象?

    不,你的 StringWriter

    那么,将编码设置为UTF-16(在xml标记中)不起作用吗?

    不需要提供XML声明。如果缺少,则假定编码为UTF-16 LE 如果 将字符串作为 )或 XML )。如果作为传入,则假定编码为默认的8位代码页 瓦尔查尔 (即。 SqlDbType.VarChar 瓦尔查尔 ,则您可能会看到“?”表示BMP字符,而“?”表示补充字符,因为SQL Server将从.NET将UTF-16字符串转换为当前数据库代码页的8位字符串,然后再将其转换回UTF-16/UCS-2。但是你不应该有任何错误。

    另一方面,如果指定了XML声明,那么 必须 使用匹配的8位或16位数据类型传递到SQL Server。因此,如果您有一个声明,声明编码是UCS-2或UTF-16,那么 作为 SqlDbType.NVarChar Xml . 或者,如果您声明编码是8位选项之一(即。 UTF-8 , Windows-1252 iso-8859-1 等),然后你 作为 SqlDbType.VarChar . 如果无法将声明的编码与正确的8位或16位SQL Server数据类型匹配,将导致您收到的“无法切换编码”错误。

    例如,使用 编剧 -基于序列化代码,我只需打印XML的结果字符串并在SSMS中使用它。正如您在下面看到的,包含了XML声明(因为 编剧 无法选择 OmitXmlDeclaration 喜欢 XmlWriter

    -- 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>
    

    正如您所看到的,它甚至可以处理超出标准ASCII的字符 ሴ 是BMP代码点U+1234,并且 😸 是补充字符代码点U+1F638。但是,以下方面:

    -- 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 . 切换到 而且它不需要经过额外的步骤来删除XML声明,就可以工作。这比保存更可取 SqlDbType.VarChar

    -- 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>
    

        6
  •  -2
  •   DLG    12 年前

    其他地方可能已经讨论过,但只要将XML源的编码行更改为“utf-16”,就可以将XML插入到SQL Server“XML”数据类型中。

    using (DataSetTableAdapters.SQSTableAdapter tbl_SQS = new DataSetTableAdapters.SQSTableAdapter())
    {
        try
        {
            bodyXML = @"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><test></test>";
            bodyXMLutf16 = bodyXML.Replace("UTF-8", "UTF-16");
            tbl_SQS.Insert(messageID, receiptHandle, md5OfBody, bodyXMLutf16, sourceType);
        }
        catch (System.Data.SqlClient.SqlException ex)
        {
            Console.WriteLine(ex.Message);
            Console.ReadLine();
        }
    }
    

    结果是所有XML文本都被插入到“XML”数据类型字段中,但“header”行被删除。您在结果记录中看到的只是

    <test></test>
    

    使用“已应答”项中描述的序列化方法是将原始头包含在目标字段中的一种方法,但结果是剩余的XML文本包含在XML文本中 <string></string> 标签