提问人:StampedeXV 提问时间:10/14/2009 最后编辑:Solomon RutzkyStampedeXV 更新时间:11/29/2022 访问量:171567
使用 StringWriter 进行 XML 序列化
Using StringWriter for XML Serialization
问:
我目前正在寻找一种简单的方法来序列化对象(在 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());
看完这个问题后,我问自己,为什么不使用 StringWriter?这似乎容易得多。
XmlSerializer ser = new XmlSerializer(typeof(MyObject));
StringWriter writer = new StringWriter();
ser.Serialize(writer, myObject);
serializedValue = writer.ToString();
另一个问题是,第一个示例生成的 XML 我不能只写入 SQL Server 2005 DB 的 XML 列。
第一个问题是:当我以后需要将 Object 作为字符串时,我是否应该使用 StringWriter 来序列化它,这有什么原因吗?谷歌搜索时,我从未使用 StringWriter 找到结果。
当然,第二个是:如果你不应该用 StringWriter 来做(无论出于什么原因),哪个会是一个好而正确的方法?
加法:
正如两个答案都已经提到的那样,我将进一步讨论 XML 到 DB 的问题。
写入数据库时,我收到以下异常:
System.Data.SqlClient.SqlException: XML 解析:第 1 行,字符 38, 无法切换编码
对于字符串
<?xml version="1.0" encoding="utf-8"?><test/>
我采用了从 XmlTextWriter 创建的字符串,并在那里放置了 xml。这个不起作用(手动插入数据库也不起作用)。
之后,我尝试使用 encoding=“utf-16” 手动插入(只是写 INSERT INTO ...),但也失败了。 删除编码完全有效。在得到这个结果之后,我切换回了 StringWriter 代码,瞧 - 它起作用了。
问题:我真的不明白为什么。
在 Christian Hayter:通过这些测试,我不确定我是否必须使用 utf-16 来写入数据库。那么将编码设置为 UTF-16(在 xml 标签中)不起作用吗?
答:
一个问题是,默认情况下,它不允许你设置它所公布的编码 - 所以你最终会得到一个XML文档,将其编码宣传为UTF-16,这意味着如果你把它写到一个文件中,你需要把它编码为UTF-16。不过,我有一个小班级可以帮助解决这个问题:StringWriter
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保存到数据库中 - 如果你希望我们能够诊断/修复它,你必须向我们提供有关你尝试时发生的情况的更多细节。
评论
StringWriter
MemoryStream
StreamWriter
StreamWriter
TextWriter
XmlWriter.Create
将 XML 文档序列化为 .NET 字符串时,编码必须设置为 UTF-16。字符串在内部以 UTF-16 格式存储,因此这是唯一有意义的编码。如果要以不同的编码存储数据,请改用字节数组。
SQL Server 的工作原理与此类似;传递到列中的任何字符串都必须编码为 UTF-16。SQL Server 将拒绝 XML 声明未指定 UTF-16 的任何字符串。如果 XML 声明不存在,则 XML 标准要求它默认为 UTF-8,因此 SQL Server 也会拒绝该声明。xml
考虑到这一点,这里有一些用于进行转换的实用方法。
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);
}
}
}
评论
StringWriter
Nothing
Deserialize
Serialize
首先,谨防寻找旧例子。您已经找到一个使用 ,该版本自 .NET 2.0 起已弃用。 应该改用。XmlTextWriter
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();
}
}
}
}
}
评论
XmlReader
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;
}
它可能已在其他地方介绍过,但只需将 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>
代码中的表适配器是使用 Visual Studio 2013“添加新数据源:”向导自动生成的类。Insert 方法的五个参数映射到 SQL Server 表中的字段。
评论
<TL;DR>实际上,问题很简单:您没有将声明的编码(在 XML 声明中)与输入参数的数据类型匹配。如果手动添加到字符串中,则声明 to 的类型为 or 将出现“无法切换编码”错误。然后,当通过 T-SQL 手动插入时,由于您将声明的编码切换为 ,因此您显然插入了一个字符串(不以大写字母“N”为前缀,因此是 8 位编码,例如 UTF-8)而不是字符串(以大写字母“N”为前缀,因此是 16 位 UTF-16 LE 编码)。<?xml version="1.0" encoding="utf-8"?><test/>
SqlParameter
SqlDbType.Xml
SqlDbType.NVarChar
utf-16
VARCHAR
NVARCHAR
修复应该像以下简单一样简单:
- 在第一种情况下,当添加声明 : 时,不要添加 XML 声明。
encoding="utf-8"
- 在第二种情况下,当添加声明时,声明 :
encoding="utf-16"
- 只是不要添加 XML 声明,或者
- 只需在输入参数类型中添加一个“N”:而不是 :-)(甚至可能切换到使用
SqlDbType.NVarChar
SqlDbType.VarChar
SqlDbType.Xml
)
(详细回复如下)
这里的所有答案都过于复杂和没有必要(无论克里斯蒂安和乔恩的答案分别有 121 票和 184 票)。他们可能会提供工作代码,但实际上没有一个回答了这个问题。问题在于没有人真正理解这个问题,这个问题最终是关于 SQL Server 中的 XML 数据类型如何工作的。这并不反对这两个明显聪明的人,但这个问题与序列化为 XML 几乎没有关系。将 XML 数据保存到 SQL Server 中比此处所暗示的要容易得多。
只要遵循如何在 SQL Server 中创建 XML 数据的规则,如何生成 XML 并不重要。在对这个问题的回答中,我有一个更彻底的解释(包括工作示例代码来说明下面概述的要点):如何解决将 XML 插入 SQL Server 时的“无法切换编码”错误,但基础知识是:
- XML 声明是可选的
- XML 数据类型始终将字符串存储为 UCS-2 / UTF-16 LE
- 如果您的 XML 是 UCS-2 / UTF-16 LE,则您可以:
- 将数据传入为 or / (maxsize = -1) 或 ,或者如果使用字符串文字,则必须以大写字母“N”为前缀。
NVARCHAR(MAX)
XML
SqlDbType.NVarChar
SqlDbType.Xml
- 如果指定 XML 声明,则它必须是“UCS-2”或“UTF-16”(此处没有实际区别)
- 将数据传入为 or / (maxsize = -1) 或 ,或者如果使用字符串文字,则必须以大写字母“N”为前缀。
- 如果您的 XML 是 8 位编码(例如“UTF-8”/“iso-8859-1”/“Windows-1252”),则您:
- 如果编码与数据库默认排序规则指定的代码页不同,则需要指定 XML 声明
- 必须以 / (maxsize = -1) 的形式传入数据,或者如果使用字符串文本,则不得以大写字母“N”为前缀。
VARCHAR(MAX)
SqlDbType.VarChar
- 无论使用哪种 8 位编码,XML 声明中注明的“编码”都必须与字节的实际编码相匹配。
- 8 位编码将按 XML 数据类型转换为 UTF-16 LE
考虑到上述几点,并考虑到 .NET 中的字符串始终是 UTF-16 LE / UCS-2 LE(它们在编码方面没有区别),我们可以回答您的问题:
当我以后需要 String Writer 作为字符串时,我不应该使用 StringWriter 来序列化 Object 有什么原因吗?
不,您的代码似乎很好(至少我在使用问题中的第二个代码块的有限测试中没有看到任何问题)。StringWriter
那么将编码设置为 UTF-16(在 xml 标签中)不起作用吗?
无需提供 XML 声明。如果缺少它,则如果将字符串作为 (i.e. ) 或 (i.e. ) 传递到 SQL Server,则假定编码为 UTF-16 LE。如果传入为 (即 )。如果您有任何非标准 ASCII 字符(即值 128 及以上)并且以 的形式传入,则您可能会看到 BMP 字符的“?”和补充字符的“??”,因为 SQL Server 会将 .NET 中的 UTF-16 字符串转换为当前数据库代码页的 8 位字符串,然后再将其转换回 UTF-16/UCS-2。但你不应该得到任何错误。NVARCHAR
SqlDbType.NVarChar
XML
SqlDbType.Xml
VARCHAR
SqlDbType.VarChar
VARCHAR
另一方面,如果指定了 XML 声明,则必须使用匹配的 8 位或 16 位数据类型传递到 SQL Server。因此,如果您有一个声明声明编码为 UCS-2 或 UTF-16,则必须传入 as 或 。或者,如果您有一个声明声明编码是 8 位选项之一(即 、 、 等),则必须作为 传入。如果声明的编码与正确的 8 位或 16 位 SQL Server 数据类型不匹配,将导致出现“无法切换编码”错误。SqlDbType.NVarChar
SqlDbType.Xml
UTF-8
Windows-1252
iso-8859-1
SqlDbType.VarChar
例如,使用基于 -的序列化代码,我只需打印生成的 XML 字符串,并在 SSMS 中使用它。正如您在下面看到的,XML 声明包含在内(因为没有 like 的选项),只要您将字符串作为正确的 SQL Server 数据类型传入,这就不会造成问题:StringWriter
StringWriter
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
因此,撇开所有这些解释不谈,您原始问题的完整解决方案是:
您显然将字符串传入为 .切换到 它将工作,而无需执行删除 XML 声明的额外步骤。这比保留和删除 XML 声明更可取,因为当 XML 包含非标准 ASCII 字符时,此解决方案将防止数据丢失。例如:SqlDbType.VarChar
SqlDbType.NVarChar
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>
如您所见,这次没有错误,但现在有 数据丢失 🙀 .
评论
SqlDbType.NVarChar
Xml
对于需要已批准答案的 F# 版本的任何人:
type private Utf8StringWriter() =
inherit StringWriter()
override _.Encoding = System.Text.Encoding.UTF8
评论