Вам нужен тип, способный представлять древовидную структуру. В каркасе можно использовать несколько типов - например, KeyValuePair<TKey, TValue>
, узел древовидного представления TreeNode
, элементы XML XmlElement
и XElement
и, возможно, некоторые другие. В следующем примере представлены два решения, использующие XElement
для представления дерева. Один использует лямбды для доступа к членам, другой использует строки, и оба имеют свои плюсы и минусы. Я предполагаю, что можно получить лучшее от решений с более сложным кодом.
static void Main()
{
IEnumerable<Invoice> invoices = new List<Invoice>()
{
new Invoice() { Id = 1, Customer = "a", Date = DateTime.Parse("1/1/2009") },
new Invoice() { Id = 2, Customer = "a", Date = DateTime.Parse("1/2/2009") },
new Invoice() { Id = 3, Customer = "a", Date = DateTime.Parse("1/2/2009") },
new Invoice() { Id = 4, Customer = "b", Date = DateTime.Parse("1/1/2009") },
new Invoice() { Id = 5, Customer = "b", Date = DateTime.Parse("1/1/2009") },
new Invoice() { Id = 6, Customer = "b", Date = DateTime.Parse("1/2/2009") }
};
StringBuilder sb = new StringBuilder();
TextWriter tw = new StringWriter(sb);
using (XmlWriter xmlWriter = new XmlTextWriter(tw) { Formatting = Formatting.Indented })
{
XElement t1 = new XElement("Root", BuildTree(invoices, i => i.Customer, i => i.Date, i => i.Id));
XElement t2 = new XElement("Root", BuildTree(invoices, "Customer", "Date", "Id"));
var xyz = t2.Elements("Customer").ElementAt(1).Descendants("Item").Count();
t1.WriteTo(xmlWriter);
t2.WriteTo(xmlWriter);
}
Console.WriteLine(sb.ToString());
Console.ReadLine();
}
public static IEnumerable<XElement> BuildTree<T>(IEnumerable<T> collection, params Func<T, Object>[] groups)
{
if ((groups != null) && (groups.Length > 0))
{
return collection
.GroupBy(groups[0])
.Select(grp => new XElement(
"Group",
new XAttribute("Value", grp.Key),
BuildTree(grp, groups.Skip(1).ToArray())));
}
else
{
return collection.Select(i => new XElement("Item"));
}
}
public static IEnumerable<XElement> BuildTree<T>(IEnumerable<T> collection, params String[] groups)
{
if ((groups != null) && (groups.Length > 0))
{
return collection
.GroupBy(i => typeof(T).GetProperty(groups[0]).GetValue(i, null))
.Select(grp => new XElement(
groups[0],
new XAttribute("Value", grp.Key),
BuildTree(grp, groups.Skip(1).ToArray())));
}
else
{
return collection.Select(i => new XElement("Item"));
}
}
Выход для первого решения следующий.
<Root>
<Group Value="a">
<Group Value="2009-01-01T00:00:00">
<Group Value="1">
<Item />
</Group>
</Group>
<Group Value="2009-02-01T00:00:00">
<Group Value="2">
<Item />
</Group>
<Group Value="3">
<Item />
</Group>
</Group>
</Group>
<Group Value="b">
<Group Value="2009-01-01T00:00:00">
<Group Value="4">
<Item />
</Group>
<Group Value="5">
<Item />
</Group>
</Group>
<Group Value="2009-02-01T00:00:00">
<Group Value="6">
<Item />
</Group>
</Group>
</Group>
</Root>
Второе решение дает следующее.
<Root>
<Customer Value="a">
<Date Value="2009-01-01T00:00:00">
<Id Value="1">
<Item />
</Id>
</Date>
<Date Value="2009-02-01T00:00:00">
<Id Value="2">
<Item />
</Id>
<Id Value="3">
<Item />
</Id>
</Date>
</Customer>
<Customer Value="b">
<Date Value="2009-01-01T00:00:00">
<Id Value="4">
<Item />
</Id>
<Id Value="5">
<Item />
</Id>
</Date>
<Date Value="2009-02-01T00:00:00">
<Id Value="6">
<Item />
</Id>
</Date>
</Customer>
</Root>
Эти решения далеки от совершенства, но могут предложить что-то для начала, и они дают полную мощность LINQ to XML для запросов к дереву. Если вы собираетесь интенсивно использовать эти деревья, я предлагаю создать тип узла для дерева, который лучше соответствует потребностям. Но, вероятно, это будет довольно сложно спроектировать, особенно если вы хотите, чтобы печатать строго.
Наконец, я хочу упомянуть, что на самом деле я не вижу возможности использования такой структуры - не будет ли намного проще использовать LINQ для объектов, чтобы получать результаты непосредственно из списка?