我有一个非常奇怪的问题,并且不知道我应该采取哪种方式来修复它。
我有一个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;
}
答案 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; }
}
}
}