字典到DataTable
我有一个非常奇怪的问题,并且不知道我应该采取哪种方式来修复它。
我有一个IEnumerable<Dictionary>
,它可以包含一个或多个IEnumerable<Dictionary>
。
现在,需要将此Dictionary导入到DataTable中,如果IEnumberable<Dictionary>
inside里面有0 IEnumberable<Dictionary>
,那么DataTable应该只包含一行,列名为字符串,RowData为对象(字符串)在这种情况下)。 但是,如果有子节点,则DataTable应包含与此子节点相同的行数,以及父节点的每一行中的其他信息。
例如,父词典具有以下值:
字符串,对象 --------------- 姓名,迈克 LastName,泰森
IEnumerable Dictionary子项有:
字符串,对象 ---------------- [0] ChildName,约翰 ChildAge,10岁 [1] ChildName,Tony ChildAge,12岁
结果应该是:
名称LastName ChildName ChildAge -------------------------------------------- Mike Tyson John 10 迈克泰森托尼12
另外,Parent IEnumerable可以有许多子IEnumerable,但它们都具有相同的大小。
有谁知道如何解决这个问题?
static void Main(string[] args) { var child1 = new List<Dictionary>(); var childOneDic = new Dictionary { { "ChildName", "John" }, { "ChildAge", 10 } }; child1.Add(childOneDic); var child2 = new List<Dictionary>(); var childTwoDic = new Dictionary { { "ChildName", "Tony" }, { "ChildAge", 12 } }; child2.Add(childTwoDic); var parrent = new List<Dictionary>(); var parrentDic = new Dictionary { { "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> 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>) 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是这份工作的好人选。 我仍然认为你应该重新考虑设计,这是一件非常可怕的事情。 这应该做(并且没有任何硬编码):
var child1 = new List> { new Dictionary { { "ChildName", "John" }, { "ChildAge", 10 } } }; var child2 = new List> { new Dictionary { { "ChildName", "Tony" }, { "ChildAge", 12 } } }; var parent = new List> { new Dictionary { { "Name", "Mike" }, { "LastName", "Tyson" }, { "child1", child1 }, { "child2", child2 } }, new Dictionary { { "Name", "Lykke" }, { "LastName", "Li" }, { "child1", child1 }, }, new Dictionary { { "Name", "Mike" }, { "LastName", "Oldfield" } } }; CreateTable(parent); static DataTable CreateTable(IEnumerable> parents) { var table = new DataTable(); foreach (var parent in parents) { var children = parent.Values .OfType>>() .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() .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 Repeat (this IEnumerable source, int times) { source = source.ToArray(); return Enumerable.Range(0, times).SelectMany(_ => source); }
你可以用更惯用的方式创建addedRows
变量(我更喜欢),但对其他人来说addedRows
。 在一行中,像这样:
var addedRows = Enumerable.Range(0, length) .Select(x => new { relativeIndex = x, actualIndex = table.Rows.IndexOf(table.Rows.Add()) }) .ToArray();
这里棘手的部分是让旋转正确。 在我们的案例中没什么大不了的,因为我们可以利用索引器 用一组例子进行测试,让我知道这是否有问题。
另一种方法是预先计算标题(循环之前的数据表列),因为它无论如何都不会改变。 但这也意味着一轮额外的枚举。 至于哪个更高效,你将不得不测试它..我发现第一个更优雅。
static DataTable CreateTable(IEnumerable> parents) { var table = new DataTable(); //excuse the meaningless variable names var c = parents.FirstOrDefault(x => x.Values .OfType>>() .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() : c.Values .OfType>>() .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>>() .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; }
我建议你使用类,这样可以更容易地查看和操作数据。 以下是基于您给出的示例可以执行的操作的示例。 诀窍还在于保留孩子内部父母的引用。 你只需要将子列表传递给网格。
static void Main() { var child1 = new List>(); var childOneDic = new Dictionary { { "ChildName", "John" }, { "ChildAge", 10 } }; child1.Add(childOneDic); var child2 = new List>(); var childTwoDic = new Dictionary { { "ChildName", "Tony" }, { "ChildAge", 12 } }; child2.Add(childTwoDic); var parrent = new List>(); var parrentDic = new Dictionary { { "Name", "Mike" }, { "LastName", "Tyson" }, { "child1", child1 }, { "child2", child2 } }; parrent.Add(parrentDic); List goodList = new List (); List allChilds = new List (); foreach (Dictionary 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 _childs = new List (); private Dictionary _dto; public Parent(Dictionary dto) { _dto = dto; for (int i = 0; i <= 99; i++) { if (_dto.ContainsKey("child" + i)) { _childs.Add(new Child(((List>)_dto["child" + i])[0], this)); } } } public string Name { get { return (string)_dto["Name"]; } } public string LastName { get { return (string)_dto["LastName"]; } } public List Childs { get { return _childs; } } } public class Child { private Parent _parent; private Dictionary _dto; public Child(Dictionary 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; } } } }