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

XML序列化文件结果

我能够通过使用可序列化字典获得至少可序列化的内容: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;
}