XML序列化文件结果
本文关键字:结果 文件 序列化 XML | 更新日期: 2023-09-27 18:17:53
我将SQL结果存储到一个动态列表中,该列表具有底层的DapperRow类型。我正在尝试序列化/反序列化XMLserializer抱怨的这个列表:
There was an error generating the XML document. ---> System.InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. Dapper.SqlMapper+DapperRow does not implement Add(System.Object).
是否有一种方法来解决这个问题(除了明显的将结果转换为我自己的具体对象),或者是否有可能以某种方式使DapperRow对象符合System.Xml.XMLserializer约束?
它声明我的结果数组是System.Collections.Generic.List<dynamic> {System.Collections.Generic.List<object>}
打开数组显示每个对象的类型都是object {Dapper.SqlMapper.DapperRow}
我认为因为dappperrows现在基本上是IDictionary<string, object>
, XML有问题(我不能使用任何东西,但System.Xml.XmlSerializer
,所以不要建议替代方案)。
我只是想能够把我从Dapper得到的List<dynamic>
和正确序列化和反序列化使用System.Xml.XmlSerializer
我能够通过使用可序列化字典获得至少可序列化的内容:http://weblogs.asp.net/pwelter34/444961
var results = conn.Query<dynamic>(sql, param);
var resultSet = new List<SerializableDictionary<string, object>>();
foreach (IDictionary<string, object> row in results)
{
var dict = new SerializableDictionary<string, object>();
foreach (var pair in row)
{
dict.Add(pair.Key, pair.Value);
}
resultSet.Add(dict);
}
很难看,所以我希望能有更优雅的解决方案
我认为这是不可能的。是否有可能以某种方式使DapperRow对象符合System.Xml.XMLserializer约束?
DapperRow
类是私有的,它没有无参数构造函数。
然而,你也许可以用其他方法来解决你的问题。
我建议您将复杂的序列化逻辑置于扩展方法之后。这样你的原始代码将保持干净。
我还建议使用以下模型来存储数据(尽管您可以选择不使用它,而在扩展方法背后使用您自己的逻辑):
public class Cell
{
public string Name { get; set; }
public object Value { get; set; }
public Cell(){}
public Cell(KeyValuePair<string, object> kvp)
{
Name = kvp.Key;
Value = kvp.Value;
}
}
public class Row : List<Cell>
{
public Row(){}
public Row(IEnumerable<Cell> cells)
: base(cells){}
}
public class Rows : List<Row>
{
public Rows(){}
public Rows(IEnumerable<Row> rows )
:base(rows){}
}
然后扩展方法应该是这样的:
public static class Extensions
{
public static void Serialize(this IEnumerable<dynamic> enumerable, Stream stream)
{
var rows =
new Rows(
enumerable
.Cast<IEnumerable<KeyValuePair<string, object>>>()
.Select(row =>
new Row(row.Select(cell => new Cell(cell)))));
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
serializer.Serialize(stream, rows);
}
}
那么你就可以使用下面的代码:
var result = connection.Query("SELECT * From Customers");
var memory_stream = new MemoryStream();
result.Serialize(memory_stream);
看看这段代码是如何非常小的,因为所有复杂的逻辑都被移动到扩展方法中。
我建议的模型也允许反序列化,只要确保使用正确的类型(例如Rows
),就像这样:
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
Rows rows = (Rows)serializer.Deserialize(stream);
你也可以有一个扩展方法,只把Dapper的结果集转换成Rows
类型,自己处理Rows
的序列化。这样的扩展方法应该像这样:
public static Rows ToRows(this IEnumerable<dynamic> enumerable)
{
return
new Rows(
enumerable
.Cast<IEnumerable<KeyValuePair<string, object>>>()
.Select(row =>
new Row(row.Select(cell => new Cell(cell)))));
}
然后像这样使用:
var rows = connection.Query("SELECT * From Customers").ToRows();
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
serializer.Serialize(stream, rows);
首先,用[Serializable]
属性装饰DapperResultSet
。还创建一个构造函数,并在其中为Rows
分配一个空的List<object>
。使用修改后的代码:(它还包含一个修改后的实现方法)
[Serializable]
public class DapperResultSet : IEnumerable<object>
{公共列表行{get;设置;}
public void Add(dynamic o)
{
Rows.Add(o);
}
public DapperResultSet()
{
Rows = new List<object>();
}
public IEnumerator<object> GetEnumerator()
{
return Rows.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
事件处理程序中的下一个(或您想要进行序列化的地方):
var results = conn.Query<dynamic>(sql, param);
var r = new DapperResultSet();
foreach (var row in results)
{
r.Add(row);
}
//Here is the serialization part:
XmlSerializer xs = new XmlSerializer(typeof(DapperResultSet));
xs.Serialize(new FileStream("Serialized.xml", FileMode.Create), r); //Change path if necessary
对于反序列化,
XmlSerializer xs = new XmlSerializer(typeof(DapperResultSet));
DapperResultSet d_DRS = xs.Deserialize(new FileStream("Serialized.xml", FileMode.Open)); //Deserialized
具有挑战性的请求,因为Dapper不是设计为可序列化的。但让我们看看能做些什么。
第一个决定很简单——我们需要实现IXmlSerializable
。问题是怎么做。
序列化不是什么大问题,因为我们有字段名和值。所以我们可以使用与您提到的SerializableDictionary<TKey, TValue>
类似的方法。然而,它严重依赖于typeof(TKey)
和typeof(TValue)
'。我们对key没有问题(它是一个字符串),但是值的类型是object
。正如我提到的,将对象值写成XML不是问题。问题是反序列化。在这一点上,我们所拥有的只是一个字符串并且不知道这个字符串是什么。这意味着我们需要存储一些元数据,以便能够正确地反序列化。当然,有很多方法可以做到这一点,但我决定在开始时分别存储字段名和类型,然后仅存储具有值的项。
把所有这些放在一起,这是我最终的结果:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Samples
{
public class DapperResultSet : IXmlSerializable
{
static readonly Type TableType;
static readonly Type RowType;
static readonly Func<object, string[]> GetFieldNames;
static readonly Func<object, object[]> GetFieldValues;
static readonly Func<string[], object> CreateTable;
static readonly Func<object, object[], object> CreateRow;
static DapperResultSet()
{
TableType = typeof(Dapper.SqlMapper).GetNestedType("DapperTable", BindingFlags.NonPublic);
RowType = typeof(Dapper.SqlMapper).GetNestedType("DapperRow", BindingFlags.NonPublic);
// string[] GetFieldNames(object row)
{
var row = Expression.Parameter(typeof(object), "row");
var expr = Expression.Lambda<Func<object, string[]>>(
Expression.Field(Expression.Field(Expression.Convert(row, RowType), "table"), "fieldNames"),
row);
GetFieldNames = expr.Compile();
}
// object[] GetFieldValues(object row)
{
var row = Expression.Parameter(typeof(object), "row");
var expr = Expression.Lambda<Func<object, object[]>>(
Expression.Field(Expression.Convert(row, RowType), "values"),
row);
GetFieldValues = expr.Compile();
}
// object CreateTable(string[] fieldNames)
{
var fieldNames = Expression.Parameter(typeof(string[]), "fieldNames");
var expr = Expression.Lambda<Func<string[], object>>(
Expression.New(TableType.GetConstructor(new[] { typeof(string[]) }), fieldNames),
fieldNames);
CreateTable = expr.Compile();
}
// object CreateRow(object table, object[] values)
{
var table = Expression.Parameter(typeof(object), "table");
var values = Expression.Parameter(typeof(object[]), "values");
var expr = Expression.Lambda<Func<object, object[], object>>(
Expression.New(RowType.GetConstructor(new[] { TableType, typeof(object[]) }),
Expression.Convert(table, TableType), values),
table, values);
CreateRow = expr.Compile();
}
}
static readonly dynamic[] emptyItems = new dynamic[0];
public IReadOnlyList<dynamic> Items { get; private set; }
public DapperResultSet()
{
Items = emptyItems;
}
public DapperResultSet(IEnumerable<dynamic> source)
{
if (source == null) throw new ArgumentNullException("source");
Items = source as IReadOnlyList<dynamic> ?? new List<dynamic>(source);
}
XmlSchema IXmlSerializable.GetSchema() { return null; }
void IXmlSerializable.WriteXml(XmlWriter writer)
{
if (Items.Count == 0) return;
// Determine field names and types
var fieldNames = GetFieldNames((object)Items[0]);
var fieldTypes = new TypeCode[fieldNames.Length];
for (int count = 0, i = 0; i < Items.Count; i++)
{
var values = GetFieldValues((object)Items[i]);
for (int c = 0; c < fieldTypes.Length; c++)
{
if (fieldTypes[i] == TypeCode.Empty && values[c] != null)
{
fieldTypes[i] = Type.GetTypeCode(values[c].GetType());
if (++count >= fieldTypes.Length) break;
}
}
}
// Write fields
writer.WriteStartElement("Fields");
writer.WriteAttributeString("Count", XmlConvert.ToString(fieldNames.Length));
for (int i = 0; i < fieldNames.Length; i++)
{
writer.WriteStartElement("Field");
writer.WriteAttributeString("Name", fieldNames[i]);
writer.WriteAttributeString("Type", XmlConvert.ToString((int)fieldTypes[i]));
writer.WriteEndElement();
}
writer.WriteEndElement();
// Write items
writer.WriteStartElement("Items");
writer.WriteAttributeString("Count", XmlConvert.ToString(Items.Count));
foreach (IDictionary<string, object> item in Items)
{
writer.WriteStartElement("Item");
foreach (var entry in item)
{
writer.WriteStartAttribute(entry.Key);
writer.WriteValue(entry.Value);
writer.WriteEndAttribute();
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
reader.MoveToContent();
bool isEmptyElement = reader.IsEmptyElement;
reader.ReadStartElement(); // Container
if (isEmptyElement) return;
// Read fields
int fieldCount = XmlConvert.ToInt32(reader.GetAttribute("Count"));
reader.ReadStartElement("Fields");
var fieldNames = new string[fieldCount];
var fieldTypes = new TypeCode[fieldCount];
var fieldIndexByName = new Dictionary<string, int>(fieldCount);
for (int c = 0; c < fieldCount; c++)
{
fieldNames[c] = reader.GetAttribute("Name");
fieldTypes[c] = (TypeCode)XmlConvert.ToInt32(reader.GetAttribute("Type"));
fieldIndexByName.Add(fieldNames[c], c);
reader.ReadStartElement("Field");
}
reader.ReadEndElement();
// Read items
int itemCount = XmlConvert.ToInt32(reader.GetAttribute("Count"));
reader.ReadStartElement("Items");
var items = new List<dynamic>(itemCount);
var table = CreateTable(fieldNames);
for (int i = 0; i < itemCount; i++)
{
var values = new object[fieldCount];
if (reader.MoveToFirstAttribute())
{
do
{
var fieldName = reader.Name;
var fieldIndex = fieldIndexByName[fieldName];
values[fieldIndex] = Convert.ChangeType(reader.Value, fieldTypes[fieldIndex], CultureInfo.InvariantCulture);
}
while (reader.MoveToNextAttribute());
}
reader.ReadStartElement("Item");
var item = CreateRow(table, values);
items.Add(item);
}
reader.ReadEndElement(); // Items
reader.ReadEndElement(); // Container
Items = items;
}
}
}
如果我们修改Dapper源代码,有些事情会更容易,但我假设你不想这样做。
下面是一个示例用法:
static void Test(IEnumerable<dynamic> source)
{
var stream = new MemoryStream();
var sourceSet = new DapperResultSet(source);
var serializer = new XmlSerializer(typeof(DapperResultSet));
serializer.Serialize(stream, sourceSet);
stream.Position = 0;
var reader = new StreamReader(stream);
var xml = reader.ReadToEnd();
stream.Position = 0;
var deserializer = new XmlSerializer(typeof(DapperResultSet));
var target = ((DapperResultSet)deserializer.Deserialize(stream)).Items;
}