XML Serializing dapper结果

时间:2015-10-14 16:08:21

标签: c# xml generics serialization dapper

我将SQL结果存储到动态List中,该List具有基础DapperRow类型。我正在尝试序列化/反序列化哪个XMLserializer抱怨的List:

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}

我认为,因为DapperRows现在基本上是IDictionary<string, object>,因为XML存在问题(我不能使用System.Xml.XmlSerializer以外的任何内容,所以不建议替代方案。)

我只是希望能够使用List<dynamic>

来转换我从Dapper获得的System.Xml.XmlSerializer并正确序列化和反序列化

4 个答案:

答案 0 :(得分:1)

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

它的丑陋,所以我希望有更优雅的解决方案出现

答案 1 :(得分:0)

首先,使用DapperResultSet属性装饰[Serializable]。同时创建一个构造函数,然后为Rows分配一个空List<object>。使用此修改后的代码:(它还包含修改后的实现方法)

[Serializable]
public class DapperResultSet : IEnumerable<object>

{     public List Rows {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

答案 2 :(得分:0)

  

是否有可能以某种方式使DapperRow对象符合System.Xml.XMLserializer约束?

我不认为这是可能的。 DapperRow类是私有的,它没有无参数构造函数。

但是,您可以使用其他方法来解决问题。

我建议您将复杂的序列化逻辑放在extension method之后。这样您的原始代码就会保持干净。

我还建议使用以下模型来存储数据(尽管您可以选择不使用它并在扩展方法后面使用您自己的逻辑):

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

答案 3 :(得分:0)

具有挑战性的请求,因为Dapper并非设计为可序列化的。但是,让我们看看可以做些什么。

第一个决定很简单 - 我们需要实施IXmlSerializable。问题是如何。

序列化并不是什么大问题,因为我们有字段名称和值。所以我们可以对你提到的SerializableDictionary<TKey, TValue>使用类似的方法。但是,它严重依赖于typeof(TKey)typeof(TValue)'。键没有问题(它是一个字符串),但值的类型是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;
}