字典<字符串,对象>到数据表 [英] Dictionary&lt;string, object&gt; to DataTable

查看:24
本文介绍了字典<字符串,对象>到数据表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个很奇怪的问题,不知道我应该采取什么方法来解决它.

我有一个 IEnumerable>,它可以包含一个或多个 IEnumerable>.

现在,这个Dictionary需要导入到DataTable中,如果里面的IEnumberable有0个children,那么DataTable应该只包含一行和Column名称为字符串,RowData 为对象(在本例中为字符串).但是,如果有一个孩子,那么 DataTable 应该包含与这个孩子相同的行数,以及来自父级的每一行中的其他信息.

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

<前>字符串,对象---------------姓名,迈克姓氏,泰森

IEnumerable 字典子有:

<前>字符串,对象----------------[0]孩子的名字,约翰儿童年龄, 10[1]孩子的名字,托尼儿童年龄, 12

结果应该是:

<前>Name LastName ChildName ChildAge-----------------------------------------------迈克泰森约翰 10迈克泰森托尼 12

此外,父 IEnumerable 可以有多个子 IEnumerable,但它们的大小都相同.

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

static void Main(string[] args){var child1 = new List>();var childOneDic = new Dictionary{{孩子姓名",约翰"},{儿童年龄",10}};child1.Add(childOneDic);var child2 = new List>();var childTwoDic = new Dictionary{{孩子姓名",托尼"},{ "ChildAge", 12 }};child2.Add(childTwoDic);var parrent = new List>();var parrentDic = new Dictionary{{姓名",迈克"},{姓氏",泰森"},{ "child1", child1 },{ "child2", child2 }};parrent.Add(parrentDic);var table = new DataTable();table.Columns.Add("名称");table.Columns.Add("LastName");table.Columns.Add("ChildName");table.Columns.Add("ChildAge");table = CreateTable(parrent, null, table);}静态数据表 CreateTable(IEnumerable> parrent,DataRow 行,DataTable 表){如果(行==空){row = table.NewRow();}foreach (var v in parrent){foreach (var o in v){if (o.Value.GetType().IsGenericType){var dic = (IEnumerable) o.Value;CreateTable(dic, row, table);}别的{行[o.Key] = o.Value;}}if (row.RowState == DataRowState.Added){DataRow tempRow = table.NewRow();tempRow.ItemArray = row.ItemArray;table.Rows.Add(tempRow);}别的{table.Rows.Add(row);}}返回表;}

解决方案

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

var child1 = new List>{新字典<字符串,对象>{ { "ChildName", "John" }, { "ChildAge", 10 } }};var child2 = new List>{新字典<字符串,对象>{ { "ChildName", "Tony" }, { "ChildAge", 12 } }};var parent = new List>{新字典<字符串,对象>{{姓名",迈克"},{姓氏",泰森"},{ "child1", child1 },{ "child2", child2 }},新字典<字符串,对象>{{名称",Lykke"},{姓氏",李"},{ "child1", child1 },},新字典<字符串,对象>{{姓名",迈克"},{姓氏",旧田"}}};创建表(父);静态数据表 CreateTable(IEnumerable> parents){var table = new DataTable();foreach(父母中的var parent){var children = parent.Values.OfType<IEnumerable<IDictionary<string, object>>>().ToArray();var length = children.Any() ?children.Length : 1;var parentEntries = parent.Where(x => x.Value 是字符串).重复(长度).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).除了(表.列.Cast().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(table.Columns 中的 DataColumn col){object[] columnRows;if (!allEntries.TryGetValue(col.ColumnName, out columnRows))继续;for (int i = 0; i < addedRows.Length; i++)table.Rows[ addedRows[i]][col] = columnRows[i];}}返回表;}

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

public static IEnumerable;重复(这个IEnumerable源,整数次){source = source.ToArray();return Enumerable.Range(0, times).SelectMany(_ => source);}

您可以以更惯用的方式(我更喜欢)创建 addedRows 变量,但对于其他人来说可能不太容易阅读.在一行中,像这样:

var addedRows = Enumerable.Range(0, length).Select(x => new{相对索引 = x,实际索引 = table.Rows.IndexOf(table.Rows.Add())}).ToArray();

这里的棘手部分是正确旋转.在我们的案例中没什么大不了的,因为我们可以使用索引器.用一组例子进行测试,如果这有问题,请告诉我..

<小时>

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

static DataTable CreateTable(IEnumerable> parents){var table = new DataTable();//原谅无意义的变量名var c = parents.FirstOrDefault(x => x.Values.OfType<IEnumerable<IDictionary<string, object>>>().任何());var p = c ??父母.FirstOrDefault();如果(p == 空)返回表;var headers = p.Where(x => x.Value 是字符串).Select(x => x.Key).Concat(c == null ?Enumerable.Empty() :c.价值观.OfType<IEnumerable<IDictionary<string, object>>>().第一的().SelectMany(x => x.Keys)).Select(x => new DataColumn(x)).ToArray();table.Columns.AddRange(headers);foreach(父母中的var parent){var children = parent.Values.OfType<IEnumerable<IDictionary<string, object>>>().ToArray();var length = children.Any() ?children.Length : 1;var parentEntries = parent.Where(x => x.Value 是字符串).重复(长度).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{相对索引 = x,实际索引 = table.Rows.IndexOf(table.Rows.Add())}).ToArray();foreach(table.Columns 中的 DataColumn col){object[] columnRows;if (!allEntries.TryGetValue(col.ColumnName, out columnRows))继续;foreach(addRows 中的 var 行)table.Rows[row.actualIndex][col] = columnRows[row.relativeIndex];}}返回表;}

I have a very strange issue, and no clue which way I should take to fix it.

I have an IEnumerable<Dictionary<string,object>> and it can contain one or more IEnumerable<Dictionary<string,object>>.

Now, this Dictionary needs to be imported into DataTable, and if the IEnumberable<Dictionary<string,object>> inside has 0 children, then the DataTable should contain only one row with the Column names as strings, and the RowData as objects (string in this case). But, if there is a child, then the DataTable should contains the same number of rows as this child, and other information in every row from parent.

For instance, the parent Dictionary has these values:

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

IEnumerable Dictionary child has:

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

[1]
ChildName, Tony
ChildAge, 12

Result should be:

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

Also, Parent IEnumerable can have many children IEnumerable, but they will all have the same size.

Does anyone have idea how to solve this?

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

解决方案

Linq is a good candidate for this job. I still think you should rethink about design, this is such a horrible thing to do. This should do (and without any hard coding):

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

This is one extension method I've used:

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

You can create the addedRows variable in a more idiomatic fashion (which I prefer) but may be that's little less readable for others. In a single line, like this:

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

The tricky part here is to get the pivoting right. No big deal in our case since we can utilize indexers. Do test with a set of examples and let me know if this is buggy..


One another way of doing it is to precalculate the headers (data table columns before the loop) as it's not going to change anyway. But that also means one extra round of enumeration. As to which is more efficient, you will have to test it.. I find the first one more elegant though.

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

这篇关于字典<字符串,对象>到数据表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆