C# Dictionary<string, object> 到 DataTable
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18923005/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dictionary<string, object> to DataTable
提问by Goran
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>>
.
我有一个IEnumerable<Dictionary<string,object>>
,它可以包含一个或多个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.
现在,这个Dictionary需要导入到DataTable中,如果IEnumberable<Dictionary<string,object>>
里面有0个孩子,那么DataTable应该只包含一行,列名为字符串,RowData为对象(本例中为字符串)。但是,如果有一个孩子,那么 DataTable 应该包含与这个孩子相同的行数,以及来自父级的每一行中的其他信息。
For instance, the parent Dictionary has these values:
例如,父字典具有以下值:
string, object --------------- Name, Mike LastName, Tyson
IEnumerable Dictionary child has:
IEnumerable 字典孩子有:
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.
此外,父 IEnumerable 可以有许多子 IEnumerable,但它们都具有相同的大小。
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;
}
采纳答案by nawfal
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):
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;
}
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 idiomaticfashion (which I prefer) but may be that's little less readable for others. In a single line, like this:
您可以addedRows
以更惯用的方式(我更喜欢)创建变量,但对其他人来说可能不太可读。在一行中,像这样:
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;
}
回答by the_lotus
I would suggest you use classes, that way it will be easier to see and manipulate the data. Here's an example of what you can do based on the example you gave. The trick is also to keep a reference of the parent inside the child. That you just need to pass the list of child to the grid.
我建议您使用类,这样可以更轻松地查看和操作数据。这是基于您提供的示例可以执行的操作的示例。诀窍还在于在子对象中保留父对象的引用。你只需要将孩子的列表传递给网格。
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; }
}
}
}