Willi
Willi

Reputation: 407

how to reference parent object in linq to xml

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

Answers (2)

Willi
Willi

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

MarcinJuraszek
MarcinJuraszek

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

Related Questions