C# 使用StringWriter进行XML序列化

C# 使用StringWriter进行XML序列化,c#,sql-server,xml,utf-8,xml-serialization,C#,Sql Server,Xml,Utf 8,Xml Serialization,我目前正在寻找一种简单的方法来序列化C3中的对象 我在谷歌上搜索了一些例子,得出了如下结论: MemoryStream memoryStream = new MemoryStream ( ); XmlSerializer xs = new XmlSerializer ( typeof ( MyObject) ); XmlTextWriter xmlTextWriter = new XmlTextWriter ( memoryStream, Encoding.UTF8 ); xs.Serializ

我目前正在寻找一种简单的方法来序列化C3中的对象

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

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());
读完这篇文章后,我问自己,为什么不使用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的问题

在写入数据库时,我遇到以下异常:

System.Data.SqlClient.SqlException: XML解析:第1行,字符38, 无法切换编码

对于字符串

<?xml version="1.0" encoding="utf-8"?><test/>
我从XmlTextWriter中创建了字符串,并将其作为xml放在那里。这一个在手动插入数据库时也不起作用

后来我尝试手动插入,只是将插入写入。。。编码=utf-16,也失败了。 删除编码完全奏效了。结果出来后,我切换回StringWriter代码,瞧,它成功了

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

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

StringWriter的一个问题是,默认情况下,您可能会得到一个xml文档,将其编码宣传为UTF-16,这意味着如果您将其写入文件,则需要将其编码为UTF-16。不过,我有一个小班要帮你:

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保存到数据库中,如果您希望我们能够诊断/修复它,您必须向我们提供有关您尝试时发生的情况的更多详细信息。

将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);
        }
    }
}

首先,要注意不要找旧的例子。您发现了一个使用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();
                }
            }
        }
    }
}

其他地方可能已经讨论过,但只要将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标记中

代码中的表适配器是使用Visual Studio 2013添加新数据源向导自动生成的类。Insert方法的五个参数映射到SQL Server表中的字段。

问题其实很简单:XML声明中声明的编码与输入参数的数据类型不匹配。如果手动添加到字符串,则将SqlParameter声明为SqlDbType.Xml或SqlDbType.NVarChar类型将导致无法切换编码错误。然后,当通过T-SQL手动插入时,由于您将声明的编码切换为utf-16,因此很明显,您插入的是一个VARCHAR字符串,其前缀不是大写N,因此是一个8位编码,例如utf-8,而不是一个以大写N为前缀的NVARCHAR字符串,因此是16位utf-16 LE编码

修复应该简单到:

在第一种情况下,当添加声明encoding=utf-8时:只需不添加XML声明。 在第二种情况下,当添加声明encoding=utf-16时:或者 只是不添加XML声明,或者 只需在输入参数type:SqlDbType.NVarChar而不是SqlDbType.VarChar:-中添加一个N,或者甚至可以切换到使用SqlDbType.Xml 详细答复如下

这里所有的答案都过于复杂,没有必要考虑问题 克里斯蒂安和乔恩的答案分别获得184票的支持。它们可能提供工作代码,但没有一个真正回答这个问题。问题是没有人真正理解这个问题,最终是关于SQL Server中XML数据类型如何工作的问题。这对这两个显然很聪明的人没有什么不利之处,但这个问题与序列化为XML几乎没有任何关系。将XML数据保存到SQL Server要比这里暗示的简单得多

只要您遵循如何在SQLServer中创建XML数据的规则,XML是如何生成的其实并不重要。我有一个更全面的解释,包括工作示例代码,以说明下面在回答这个问题时概述的要点:,但基本内容是:

XML声明是可选的 XML数据类型总是将字符串存储为UCS-2/UTF-16 LE 如果您的XML是UCS-2/UTF-16 LE,那么您: 以NVARCHARMAX或XML/SqlDbType.NVarChar maxsize=-1或SqlDbType.XML的形式传入数据,或者如果使用字符串文字,则必须以大写字母N作为前缀。 如果指定XML声明,则必须是UCS-2或UTF-16,此处没有实际区别 如果您的XML是8位编码的,例如UTF-8/iso-8859-1/Windows-1252,则您: 如果编码不同于数据库默认排序规则指定的代码页,则需要指定XML声明 必须以VARCHARMAX/SqlDbType.VarChar maxsize=-1的形式传入数据,或者如果使用字符串文字,则不能以大写字母N作为前缀。 无论使用何种8位编码,XML声明中注明的编码必须与字节的实际编码相匹配。 8位编码将通过XML数据类型转换为UTF-16 LE 考虑到上述要点,并考虑到.NET中的字符串始终是UTF-16 LE/UCS-2 LE,因此在编码方面没有区别,我们可以回答您的问题:

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

不,您的StringWriter代码看起来很好,至少我在使用问题中的第二个代码块进行的有限测试中没有发现任何问题

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

不需要提供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不像XmlWriter那样具有省略XmlDeclaration的选项,只要将字符串作为正确的SQL Server数据类型传入,就不会产生任何问题:

-大写N前缀==NVARCHAR,因此没有错误: 声明@Xml=N'
测验ሴ我现在详细讨论了数据库问题。请参阅问题。悲伤StringWriter没有考虑编码,但从来没有少,感谢一个漂亮的小方法:和XML解析:第1行,字符38,无法切换编码可以通过设置解决。Indent=false;settings.OmitXmlDeclaration=false;我通常通过简单地使用MemoryStream和具有正确编码的StreamWriter来解决这个问题。StreamWriter是一种文本编写器,XmlWriter.Create希望使用可自定义的编码。@nyrguds:所以用这种东西创建一个Nuget包,这样就很容易找到它。我宁愿这样做,也不愿牺牲代码的可读性,因为代码的可读性基本上是关于其他要求的。我不理解我的测试结果,这似乎与您的说法相矛盾,即DB总是想要/接受/需要UTF-16。您不必编码为UTF-16,但您可以
ave以确保您使用的编码与StringWriter期望的匹配。看看我的答案。这里的内部存储格式不相关。好的,我明白了。在我的新示例中:完全不使用编码会让DB自己决定使用哪种编码——这就是它工作的原因。我现在明白了吗?@SteveC:对不起,我弄错了。我手工转换了VB中的代码,在VB中没有任何东西可以隐式转换为任何类型。我已经更正了反序列化代码。Serialize警告必须是Resharper唯一的东西,编译器本身不反对,这样做是合法的。根据Jon Skeet的评论,不需要UTF-16。请参考一个具体的例子来说明这一点。我将讲述个人经历。SQL Server只接受UTF-16,如果您传递任何其他内容,那么您就要听命于SQL Server XML解析器及其转换数据的尝试。我没有试图找到一种愚弄它的方法,而是直接将其传递给UTF-16,这将始终有效。您如何将其写入数据库?您是在向它传递字符串、字节数组还是写入流?如果是后两种形式之一,则需要确保声明的编码与二进制数据的实际编码相匹配。我在MS SQL Management Studio中作为查询进行的手动尝试。编码的尝试被写入一个字符串,然后被传递到一个O/R映射器,该映射器尽可能以字符串的形式写入。事实上,我正在将在我的问题中给出的两个示例中创建的字符串传递给读者——几乎重复:我正在更改我接受的答案,因为我相信它确实回答了我的问题。尽管其他的答案帮助我继续我的工作,但为了我的目的,我认为所罗门的答案将帮助其他人更好地理解所发生的事情。[免责声明]:我没有时间真正验证答案。我只能对此投票一次,但这应该是这里的首要答案。最后,声明或使用什么编码并不重要,只要XmlReader能够解析它。它将被预先解析发送到数据库,然后DB不需要知道任何关于字符编码的信息—UTF-16或其他信息。特别要注意的是,XML声明甚至不会与数据库中的数据一起持久化,无论使用哪种方法插入数据。请不要通过额外的转换来运行XML造成浪费,如这里和其他地方的其他答案所示。替换?这太好笑了。说真的,别这样。曾经如果我想在xml中包含一些提到UTF-8的文字,那该怎么办?您刚刚将我的数据更改为我没有说的内容!感谢您指出代码中的错误。与bodyXML.ReplaceUTF-8、UTF-16不同,UTF-16应该有一些代码关注XML头,将UTF-8更改为UTF-16。我真正想指出的是,通过对源XML的头进行这种更改,然后可以使用XML数据类型字段将XML的主体插入到SQL表记录中,并去掉头。因为我现在不记得四年前的原因!结果在当时是有用的。是的,使用“替换”是一个愚蠢的错误。我想我是这个答案过于复杂的原因,因为我基本上在一个问题中有两个问题。我真的很喜欢你简洁的答案,下次我必须在数据库中存储XML时,我会尝试一下。如果我没看错的话:您解释了将XML存储到DB的挑战。Jon Skeet总结了处理XML时使用StringWriter的问题,UTF-16除外,Christian Hayter提供了一种很好的方法来处理它。@StampedeXV我更新了我的答案,为清晰起见做了一些更改+新的内容,以更好地说明要点。希望现在更清楚的是,虽然这两个答案本身都很好,但它们并不是回答你问题的必要条件。它们在C/.NET中处理XML序列化,但这个问题实际上是关于在SQL Server中保存XML。它们提供的信息很好,可能比您最初提供的代码更好,但它们和这里的任何其他代码都不是真正的主题。但这并不是很好的文档,因此产生了困惑。我的修订有意义吗?我只是在顶部添加了一个可能更清晰的摘要部分。长话短说:除非你在问题中没有包含其他细节,否则你的代码似乎99%正确,并且可能通过添加一个大写字母N来修复。不需要特殊编码,而且Christian的代码很好,但我的测试表明,它返回的序列化与您的第二个代码块相同,只是您的代码块在XML声明后放了一个CRLF。我打赌您已更改为SqlDbType.NVarChar或Xml。仍在尝试自己找时间检查它。这听起来确实不错,而且合乎逻辑,但不确定这是否足以改变一个被接受的答案。
<test></test>