I have a very strange issue, and no clue which way I should take to fix it.
I have an IEnumerable
and it can cont
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;
}
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; }
}
}
}