字典<string,object =“”>到DataTable </string,>

时间:2013-09-20 17:58:09

标签: c# dictionary datatable

我有一个非常奇怪的问题,并且不知道我应该采取哪种方式来修复它。

我有一个IEnumerable<Dictionary<string,object>>,它可以包含一个或多个IEnumerable<Dictionary<string,object>>

现在,需要将此Dictionary导入到DataTable中,如果IEnumberable<Dictionary<string,object>>内部有0个子节点,则DataTable应该只包含一行,其中列名称为字符串,RowData为对象(字符串为这个案例)。但是,如果有一个子节点,那么DataTable应该包含与该子节点相同的行数,以及来自父节点的每一行中的其他信息。

例如,父词典具有以下值:

string, object
---------------
Name, Mike
LastName, Tyson

IEnumerable Dictionary child具有:

string, object
----------------
[0]
ChildName, John
ChildAge, 10

[1]
ChildName, Tony
ChildAge, 12

结果应该是:

Name    LastName     ChildName     ChildAge
--------------------------------------------
Mike    Tyson        John          10
Mike    Tyson        Tony          12

另外,Parent IEnumerable可以有许多IEnumerable,但它们都有相同的大小。

有谁知道如何解决这个问题?

static void Main(string[] args)
{
    var child1 = new List<Dictionary<string, object>>();
    var childOneDic = new Dictionary<string, object> 
    { 
        { "ChildName", "John" }, 
        { "ChildAge", 10 } 
    };
    child1.Add(childOneDic);

    var child2 = new List<Dictionary<string, object>>();
    var childTwoDic = new Dictionary<string, object> 
    { 
        { "ChildName", "Tony" }, 
        { "ChildAge", 12 } 
    };
    child2.Add(childTwoDic);

    var parrent = new List<Dictionary<string, object>>();
    var parrentDic = new Dictionary<string, object>
    {
        { "Name", "Mike" },
        { "LastName", "Tyson" },
        { "child1", child1 },
        { "child2", child2 }
    };
    parrent.Add(parrentDic);

    var table = new DataTable();
    table.Columns.Add("Name");
    table.Columns.Add("LastName");
    table.Columns.Add("ChildName");
    table.Columns.Add("ChildAge");
    table = CreateTable(parrent, null, table);
}

static DataTable CreateTable(IEnumerable<Dictionary<string, object>> parrent, 
                             DataRow row, DataTable table)
{
    if (row == null)
    {
        row = table.NewRow();
    }

    foreach (var v in parrent)
    {
        foreach (var o in v)
        {
            if (o.Value.GetType().IsGenericType)
            {
                var dic = (IEnumerable<Dictionary<string, object>>) o.Value;
                CreateTable(dic, row, table);
            }
            else
            {
                row[o.Key] = o.Value;
            }
        }
        if (row.RowState == DataRowState.Added)
        {
            DataRow tempRow = table.NewRow();
            tempRow.ItemArray = row.ItemArray;
            table.Rows.Add(tempRow);
        }
        else
        {
            table.Rows.Add(row);
        }
    }

    return table;
}

2 个答案:

答案 0 :(得分:2)

Linq是这项工作的好人选。我仍然认为你应该重新考虑设计,这是一件非常可怕的事情。这应该做(并且没有任何硬编码):

var child1 = new List<IDictionary<string, object>>
{
    new Dictionary<string, object> { { "ChildName", "John" }, { "ChildAge", 10 } }
};

var child2 = new List<IDictionary<string, object>>
{
    new Dictionary<string, object> { { "ChildName", "Tony" }, { "ChildAge", 12 } }
};

var parent = new List<IDictionary<string, object>>
{
    new Dictionary<string, object> 
    { 
        { "Name", "Mike" },
        { "LastName", "Tyson" },
        { "child1", child1 },
        { "child2", child2 }
    },
    new Dictionary<string, object>
    {
        { "Name", "Lykke" },
        { "LastName", "Li" },
        { "child1", child1 },
    },
    new Dictionary<string, object>
    { 
        { "Name", "Mike" },
        { "LastName", "Oldfield" }
    }
};

CreateTable(parent);

static DataTable CreateTable(IEnumerable<IDictionary<string, object>> parents)
{
    var table = new DataTable();

    foreach (var parent in parents)
    {
        var children = parent.Values
                             .OfType<IEnumerable<IDictionary<string, object>>>()
                             .ToArray();

        var length = children.Any() ? children.Length : 1;

        var parentEntries = parent.Where(x => x.Value is string)
                                  .Repeat(length)
                                  .ToLookup(x => x.Key, x => x.Value);
        var childEntries = children.SelectMany(x => x.First())
                                   .ToLookup(x => x.Key, x => x.Value);

        var allEntries = parentEntries.Concat(childEntries)
                                      .ToDictionary(x => x.Key, x => x.ToArray());

        var headers = allEntries.Select(x => x.Key)
                                .Except(table.Columns
                                             .Cast<DataColumn>()
                                             .Select(x => x.ColumnName))
                                .Select(x => new DataColumn(x))
                                .ToArray();
        table.Columns.AddRange(headers);

        var addedRows = new int[length];
        for (int i = 0; i < length; i++)
            addedRows[i] = table.Rows.IndexOf(table.Rows.Add());

        foreach (DataColumn col in table.Columns)
        {
            object[] columnRows;
            if (!allEntries.TryGetValue(col.ColumnName, out columnRows))
                continue;

            for (int i = 0; i < addedRows.Length; i++)
                table.Rows[addedRows[i]][col] = columnRows[i];
        }
    }

    return table;
}

这是我用过的一种扩展方法:

public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source, int times)
{
    source = source.ToArray();
    return Enumerable.Range(0, times).SelectMany(_ => source);
}

您可以使用更多惯用方式创建addedRows变量(我更喜欢这种方式),但对其他人来说可能性稍差一些。在一行中,像这样:

var addedRows = Enumerable.Range(0, length)
                          .Select(x => new
                          {
                              relativeIndex = x,
                              actualIndex = table.Rows.IndexOf(table.Rows.Add())
                          })
                          .ToArray();

这里棘手的部分是让旋转正确。由于我们可以利用索引器,因此在我们的案例中没有什用一组例子进行测试,让我知道这是否有问题......


另一种方法是预先计算标题(循环前的数据表列),因为它无论如何都不会改变。但这也意味着一轮额外的枚举。至于哪个更高效,你将不得不测试它..我发现第一个更优雅。

static DataTable CreateTable(IEnumerable<IDictionary<string, object>> parents)
{
    var table = new DataTable();

    //excuse the meaningless variable names

    var c = parents.FirstOrDefault(x => x.Values
                                         .OfType<IEnumerable<IDictionary<string, object>>>()
                                         .Any());
    var p = c ?? parents.FirstOrDefault();
    if (p == null)
        return table;

    var headers = p.Where(x => x.Value is string)
                   .Select(x => x.Key)
                   .Concat(c == null ? 
                           Enumerable.Empty<string>() : 
                           c.Values
                            .OfType<IEnumerable<IDictionary<string, object>>>()
                            .First()
                            .SelectMany(x => x.Keys))
                   .Select(x => new DataColumn(x))
                   .ToArray();
    table.Columns.AddRange(headers);

    foreach (var parent in parents)
    {
        var children = parent.Values
                             .OfType<IEnumerable<IDictionary<string, object>>>()
                             .ToArray();

        var length = children.Any() ? children.Length : 1;

        var parentEntries = parent.Where(x => x.Value is string)
                                  .Repeat(length)
                                  .ToLookup(x => x.Key, x => x.Value);
        var childEntries = children.SelectMany(x => x.First())
                                   .ToLookup(x => x.Key, x => x.Value);

        var allEntries = parentEntries.Concat(childEntries)
                                      .ToDictionary(x => x.Key, x => x.ToArray());

        var addedRows = Enumerable.Range(0, length)
                                  .Select(x => new
                                  {
                                      relativeIndex = x,
                                      actualIndex = table.Rows.IndexOf(table.Rows.Add())
                                  })
                                  .ToArray();

        foreach (DataColumn col in table.Columns)
        {
            object[] columnRows;
            if (!allEntries.TryGetValue(col.ColumnName, out columnRows))
                continue;

            foreach (var row in addedRows)
                table.Rows[row.actualIndex][col] = columnRows[row.relativeIndex];
        }
    }

    return table;
}

答案 1 :(得分:0)

我建议您使用类,这样可以更容易地查看和操作数据。以下是基于您给出的示例可以执行的操作的示例。诀窍还在于保留孩子内部父母的参考。你只需要将子列表传递给网格。

    static void Main()
    {
        var child1 = new List<Dictionary<string, object>>();
        var childOneDic = new Dictionary<string, object> 
{ 
    { "ChildName", "John" }, 
    { "ChildAge", 10 } 
};
        child1.Add(childOneDic);

        var child2 = new List<Dictionary<string, object>>();
        var childTwoDic = new Dictionary<string, object> 
{ 
    { "ChildName", "Tony" }, 
    { "ChildAge", 12 } 
};
        child2.Add(childTwoDic);

        var parrent = new List<Dictionary<string, object>>();
        var parrentDic = new Dictionary<string, object>
{
    { "Name", "Mike" },
    { "LastName", "Tyson" },
    { "child1", child1 },
    { "child2", child2 }
};
        parrent.Add(parrentDic);


        List<Parent> goodList = new List<Parent>();
        List<Child> allChilds = new List<Child>();

        foreach (Dictionary<string, object> p in parrent)
        {
            Parent newParent = new Parent(p);

            goodList.Add(newParent);
            allChilds.AddRange(newParent.Childs);
        }

        foreach (Child c in allChilds)
        {
            Console.WriteLine(c.ParentName + ":" + c.ParentName + ":" + c.Name + ":" + c.Age);
        }

        Console.ReadLine();

    }

    public class Parent
    {
        private List<Child> _childs = new List<Child>();

        private Dictionary<string, object> _dto;

        public Parent(Dictionary<string, object> dto)
        {
            _dto = dto;

            for (int i = 0; i <= 99; i++)
            {
                if (_dto.ContainsKey("child" + i))
                {
                    _childs.Add(new Child(((List<Dictionary<string, object>>)_dto["child" + i])[0], this));
                }
            }

        }

        public string Name
        {
            get { return (string)_dto["Name"]; }
        }

        public string LastName
        {
            get { return (string)_dto["LastName"]; }
        }

        public List<Child> Childs
        {
            get { return _childs; }

        }

    }

    public class Child
    {

        private Parent _parent;

        private Dictionary<string, object> _dto;

        public Child(Dictionary<string, object> dto, Parent parent)
        {
            _parent = parent;
            _dto = dto;

        }

        public string Name
        {
            get { return (string)_dto["ChildName"]; }
        }


        public int Age
        {
            get { return (int)_dto["ChildAge"]; }
        }

        public string ParentName
        {
            get { return _parent.Name; }
        }

        public string ParentLastName
        {
            get { return _parent.LastName; }
        }

    }
}