我目前正在寻找一种简单的方法来序列化对象(在 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 我不能只写入 SQL Server 2005 DB 的 XML 列。
第一个问题是:当我需要它作为字符串时,是否有理由不使用 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 标记中)不起作用吗?
StringWriter
的一个问题是默认情况下为 it doesn't let you set the encoding which it advertises - 因此您最终会得到一个将其编码宣传为 UTF-16 的 XML 文档,这意味着如果您将其写入文件,则需要将其编码为 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);
}
}
}
StringWriter
的预期。看我的回答。内部存储格式在这里无关紧要。
Nothing
可以隐式转换为任何类型。我已更正 Deserialize
代码。 Serialize
警告必须是 Resharper-only 的事情,编译器本身不会反对并且这样做是合法的。
首先,小心寻找旧的例子。您找到了一个使用 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();
}
}
}
}
}
XmlReader
可以解析它。它将预先解析后发送到数据库,然后数据库不需要知道任何关于字符编码的信息——UTF-16 或其他。特别要注意,XML 声明甚至不会与数据库中的数据一起保存,无论使用哪种方法插入它。请不要浪费通过额外的转换运行 XML,如此处和其他地方的其他答案所示。
<TL;DR> 实际上,问题相当简单:您没有将声明的编码(在 XML 声明中)与输入参数的数据类型匹配。如果您手动将 <?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 编码)。
修复应该很简单:
在第一种情况下,当添加声明 encoding="utf-8" 的声明时:根本不添加 XML 声明。在第二种情况下,当添加声明 encoding="utf-16" 的声明时:要么根本不添加 XML 声明,要么只是在输入参数类型中添加一个“N”: SqlDbType.NVarChar 而不是 SqlDbType.VarChar : -)(或者甚至可能切换到使用 SqlDbType.Xml)
(详细回复如下)
这里的所有答案都过于复杂和不必要(无论克里斯蒂安和乔恩的答案分别获得 121 票和 184 票)。他们可能会提供工作代码,但他们都没有真正回答这个问题。问题是没有人真正理解这个问题,这最终是关于 SQL Server 中的 XML 数据类型如何工作的。对这两个显然很聪明的人没有异议,但是这个问题与序列化为 XML 几乎没有关系。将 XML 数据保存到 SQL Server 中比这里所暗示的要容易得多。
只要您遵循如何在 SQL Server 中创建 XML 数据的规则,如何生成 XML 并不重要。在这个问题的答案中,我有更彻底的解释(包括说明下面概述的要点的工作示例代码):How to solve “unable to switch the encoding” error when inserting XML into SQL Server,但基础是:
XML 声明是可选的 XML 数据类型始终将字符串存储为 UCS-2 / UTF-16 LE 如果您的 XML 是 UCS-2 / UTF-16 LE,那么您: 将数据作为 NVARCHAR(MAX) 或 XML / SqlDbType 传递.NVarChar (maxsize = -1) 或 SqlDbType.Xml,或者如果使用字符串文字,则必须以大写“N”作为前缀。如果指定 XML 声明,它必须是“UCS-2”或“UTF-16”(这里没有真正的区别)如果您的 XML 是 8 位编码的(例如“UTF-8”/“iso-8859-1” /“Windows-1252”),那么您:需要指定 XML 声明 如果编码与数据库的默认排序规则指定的代码页不同,您必须将数据作为 VARCHAR(MAX) / SqlDbType.VarChar ( maxsize = -1),或者如果使用字符串文字,则不得以大写“N”为前缀。无论使用什么 8 位编码,XML 声明中注明的“编码”必须与字节的实际编码相匹配。 XML 数据类型将 8 位编码转换为 UTF-16 LE
考虑到上述几点,并且鉴于 .NET 中的字符串始终为 UTF-16 LE / UCS-2 LE(在编码方面没有区别),我们可以回答您的问题:
之后我需要它作为字符串时,我不应该使用 StringWriter 来序列化一个对象有什么原因吗?
不,您的 StringWriter
代码似乎很好(至少我在使用问题中的第二个代码块进行的有限测试中没有发现任何问题)。
那么将编码设置为 UTF-16(在 xml 标记中)不起作用吗?
不必提供 XML 声明。当它丢失时,编码假定为 UTF-16 LE if 您将字符串作为 NVARCHAR
(即 SqlDbType.NVarChar
)或 XML
(即 SqlDbType.Xml
)传递到 SQL Server .如果作为 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
等),那么您必须以 { 6}。未能将声明的编码与正确的 8 位或 16 位 SQL Server 数据类型匹配将导致您遇到的“无法切换编码”错误。
例如,使用基于 StringWriter
的序列化代码,我只需打印 XML 的结果字符串并在 SSMS 中使用它。正如您在下面看到的,包含了 XML 声明(因为 StringWriter
没有像 XmlWriter
那样的 OmitXmlDeclaration
选项),只要您将字符串作为正确的 SQL Server 数据类型传入就不会出现问题:
-- 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
传递。切换到 SqlDbType.NVarChar
,它无需执行删除 XML 声明的额外步骤即可工作。这比保留 SqlDbType.VarChar
和删除 XML 声明更可取,因为当 XML 包含非标准 ASCII 字符时,此解决方案将防止数据丢失。例如:
-- 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
。
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”数据类型字段中,但删除了“标题”行。您在结果记录中看到的只是
<test></test>
使用“Answered”条目中描述的序列化方法是将原始标头包含在目标字段中的一种方法,但结果是剩余的 XML 文本包含在 XML <string></string>
标记中。
代码中的表适配器是使用 Visual Studio 2013“添加新数据源:向导”自动构建的类。Insert 方法的五个参数映射到 SQL Server 表中的字段。
不定期副业成功案例分享
StringWriter
没有考虑到编码,但从来没有少过,感谢一个漂亮的小方法:)MemoryStream
和StreamWriter
来解决这个问题。毕竟,StreamWriter
is 是具有可自定义编码的TextWriter
(XmlWriter.Create
期望的类型)。