Reputation: 407
I have a xml document like this:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<level1>
<level2>
<level3/>
<level3/>
</level2>
<level2>
<level3/>
</level2>
</level1>
<level1>
<level2>
<level3/>
</level2>
</level1>
</root>
its corresponding clr types are those:
class Level1
{
public ObservableCollection<Level2> Children { get; set; }
}
class Level2
{
public Level1 Parent{get;set;}
public ObservableCollection<Level3> Children { get; set; }
}
class Level3
{
public Level2 Parent { get; set; }
}
and I want to load object graph from xml using linq to xml, my codes like this:
public IEnumerable<Level1> Load(XElement root)
{
var query = from l1 in root.Elements("Level1")
select new Level1()
{
Children = new ObservableCollection<Level2>(
from l2 in l1.Elements("level2")
select new Level2()
{
Children = new ObservableCollection<Level3>(
from l3 in l2.Elements("level3")
select new Level3())
})
};
foreach (Level1 l1 in query)
{
foreach (Level2 l2 in l1.Children)
{
l2.Parent = l1;
foreach (Level3 l3 in l2.Children)
{
l3.Parent = l2;
}
}
}
return query;
}
Note that there are foreach statements after the linq query just in order to give all children the reference to their parents. I wondering if there is any elegant method to eleminate those foreach statements and set the relations from children to parent just in the query expression?
Upvotes: 1
Views: 477
Reputation: 407
I have made a test xml document that has 128 level1 node like the first level1 node which is above mentioned, so that its scale is close to my actual problem space. Then I have made a performance test like this(call Test method):
public ObservableCollection<Level1> Load1(XElement root, out TimeSpan t1, out TimeSpan t2)
{
Stopwatch sp = new Stopwatch();
sp.Start();
var query = from l1 in root.Elements("level1")
select new Level1()
{
Children = new ObservableCollection<Level2>(
from l2 in l1.Elements("level2")
select new Level2()
{
Children = new ObservableCollection<Level3>(
from l3 in l2.Elements("level3")
select new Level3())
})
};
ObservableCollection<Level1> l1Collection = new ObservableCollection<Level1>(query);
sp.Stop();
t1 = sp.Elapsed;
sp.Reset();
sp.Start();
foreach (Level1 l1 in query)
{
foreach (Level2 l2 in l1.Children)
{
l2.Parent = l1;
foreach (Level3 l3 in l2.Children)
{
l3.Parent = l2;
}
}
}
sp.Stop();
t2 = sp.Elapsed;
return l1Collection;
}
public ObservableCollection<Level1> Load2(XElement root, out TimeSpan t)
{
Stopwatch sp = new Stopwatch();
sp.Start();
ObservableCollection<Level1> l1Collection = new ObservableCollection<Level1>(
root.Elements("level1").Select(xl1 =>
{
Level1 l1 = new Level1();
l1.Children = new ObservableCollection<Level2>(xl1.Elements("level2").Select(xl2 =>
{
Level2 l2 = new Level2() { Parent = l1 };
l2.Children = new ObservableCollection<Level3>(xl2.Elements("level3").Select(xl3 =>
new Level3() { Parent = l2 }));
return l2;
}));
return l1;
}));
sp.Stop();
t = sp.Elapsed;
return l1Collection;
}
public void Test()
{
XElement root = XElement.Load("xmlfile2.xml");
TimeSpan t1, t2, t3;
Load1(root, out t1, out t2);
Load2(root, out t3);
System.Windows.MessageBox.Show(string.Format("t1={0} t2={1} t3={2}", t1, t2, t3));
}
the result is: t1=00:00:00.0015920 t2=00:00:00.0004591 t3=00:00:00.0007233
I can expect that the loops will take some redundant time(t2) but I'm before never aware that method-based query runs much faster(t3) than general syntax-based query(t1). Is method-based query localized and optimized for c# environment? Under any circumstance I will in the future prefer to use method-based query.
Upvotes: 0
Reputation: 125630
I'm pretty sure it's not possible using syntax-based query, but it's definitely possible using method-based query and multi-line lambdas:
public static IEnumerable<Level1> Load(XElement root)
{
return root.Elements("level1")
.Select(l1 => {
var level1 = new Level1();
level1.Children = l1.Elements("level2").Select(l2 => {
var level2 = new Level2();
level2.Parent = level1;
level2.Children = l2.Elements("level3")
.Select(l3 => new Level3() {
Parent = level2
})
.ToList();
return level2;
}).ToList();
return level1;
});
}
PS. Sorry for changing ObservableCollection<T>
into List<T>
. I think you'll be able to make the change back to your collection without any problems.
Upvotes: 1