字典< string,object>到DataTable [英] Dictionary<string, object> to DataTable

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

问题描述

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



我有一个 IEnumerable< Dictionary<字符串,对象>> ,它可以包含一个或多个 IEnumerable<字典< string,object>>



现在,该Dictionary需要导入DataTable,如果 IEnumberable< Dictionary< string,object>> inside有0个小孩,那么DataTable应该只包含一行,列名称为字符串,RowData为对象(在这种情况下为字符串)。但是,如果有一个小孩,那么DataTable应该包含与该孩子相同数量的行,以及来自父母的每一行中的其他信息。



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

 
string,object
---------------
名称,Mike
姓氏,泰森

IEnumerable字典小孩有:

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

[1]
ChildName,Tony
ChildAge,12

结果应该是:

 
名称姓氏ChildName ChildAge
------------ --------------------------------
迈克泰森约翰10
迈克泰森Tony 12

此外,父IEnumerable可以有很多孩子IEnumerable,但都将具有相同的大小。


$ b $有没有人有想法如何解决这个问题?

  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行,DataTable表)
{
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);
}
}

返回表;
}


解决方案

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

  var child1 = new List< IDictionary< string,object>> 
{
新词典< string,object> {{ChildName,John},{ChildAge,10}}
};

var child2 = new List< IDictionary< string,object>>
{
新词典< string,object> {{ChildName,Tony},{ChildAge,12}}
};

var parent = new List< IDictionary< string,object>>
{
新词典< 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 children = parent.Values
.OfType< IEnumerable< IDictionary< string,object>>> )
.ToArray();

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

var parentEntries = parent.Where(x => x.Value是字符串)
.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>()
。 (x => x.ColumnName))
.Select(x => new DataColumn(x))
.ToArray();
table.Columns.AddRange(headers);

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


foreach(TableColumns中的DataColumn col)
{
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];
}
}

返回表;
}

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

  public static IEnumerable< T>重复< T>(此IEnumerable< T>源,int times)
{
source = source.ToArray();
返回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();

//借口无意义的变量名

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

var headers = p.Where(x => x.Value是字符串)
.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 children = parent.Values
.OfType< IEnumerable< IDictionary< string,object>>> )
.ToArray();

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

var parentEntries = parent.Where(x => x.Value是字符串)
.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(TableColumns中的DataColumn col)
{
object [] columnRows;
if(!allEntries.TryGetValue(col.ColumnName,out columnRows))
continue;

foreach(addRows中的var row)
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;
}

这篇关于字典&lt; string,object&gt;到DataTable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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