Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ to XML - How to use XDocument the right way

Now I will start off by saying this is indeed an assigment. I have however nearly finished it untill I ran into the Linq to XML syntax.

I have 2 classes: Track and CD now as part of the assigment I create a cd and then added some tracks to it. After searching for lots of tutorials that explained perfectly how to go from xml to objects I just cannot seem to get this working (objects to xml).

I currently have:

//My list of cds
List<CD> cds = new List<CD>();
//Make a new CD and add some tracks to it
CD c1 = new CD("Awake","Dream Theater");
Track t1 = new Track("6:00", "Dream Theater", new TimeSpan(00, 05, 31));
Track t2 = new Track("Caught in a Web", "Dream Theater", new TimeSpan(00, 05, 28));
Track t3 = new Track("Innocence Faded", "Dream Theater", new TimeSpan(00, 05, 34));
c1.addTrack(t1);
c1.addTrack(t2);
c1.addTrack(t3);
cds.Add(c1);

//Make another cd and add it
CD c2 = new CD("Second cd","TestArtist");
Track t4 = new Track("TrackForSecond","TestArtist",new TimeSpan(00,13,37));
c2.addTrack(t4);
cds.add(c2);

Now this is what gets me the objects I need to put into XML. The to XML part is:

XDocument xmlOutput = new XDocument (
     new XDeclaration("1.0","utf-8","yes"),
     (from cl in cds orderby cl.getArtist()
        select new XElement("cd",  /*From new to the end of this is the error*/
            (
               from c in cds
                   select new XAttribute("artist",c.getArtist())
            ),
            (
               from c in cds
                   select new XAttribute("name", c.getTitle())
            ),
            new XElement("tracks",
               (
                   from t in c1.getTracks()
                       select new XElement("track",
                           new XElement("artist",t1.getArtist()),    
                           new XElement("title",t1.getTitle()),
                           new XElement("length",t1.getLength())
                       )          
               )                    
            )
        )
    )                   
);
Console.WriteLine(xmlOutput);

This works great (gets me the result I need!) for just 1 cd. When I decide to add another cd it shows:

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.Linq.dll
Duplicate Attribute (cd)

Which is pointing at the XDocument. Aside from this not working it feels pretty stupid (from c in cds x2) but whatever I try I cannot seem to stop this syntax from hating me:

(
from c in cds
   select new XAttribute("artist",c.getArtist()),    
   select new XAttribute("name", c.getTitle()) //No not happening!
),

Would be very happy with any help you can provide!

like image 527
Floris Velleman Avatar asked May 28 '13 15:05

Floris Velleman


People also ask

Can we use LINQ for XML?

LINQ to XML is a LINQ-enabled, in-memory XML programming interface that enables you to work with XML from within the . NET programming languages. LINQ to XML is like the Document Object Model (DOM) in that it brings the XML document into memory.

Should I use XDocument or XmlDocument?

XDocument is from the LINQ to XML API, and XmlDocument is the standard DOM-style API for XML. If you know DOM well, and don't want to learn LINQ to XML, go with XmlDocument . If you're new to both, check out this page that compares the two, and pick which one you like the looks of better.

Which of the following option is created to retrieve data into XML using LINQ?

The LINQ to XML will bring the XML document into memory and allows us to write LINQ Queries on in-memory XML document to get the XML document elements and attributes. To use LINQ to XML functionality in our applications, we need to add "System. Xml. Linq" namespace reference.

What is XDocument C#?

The XDocument class contains the information necessary for a valid XML document, which includes an XML declaration, processing instructions, and comments. You only have to create XDocument objects if you require the specific functionality provided by the XDocument class.


2 Answers

First, I suggest you to use properties and C# style naming for methods. Here is how your classes could be refactored:

public class CD
{
    private readonly List<Track> _tracks = new List<Track>();

    public CD(string artist, string title)
    {
        Artist = artist;
        Title = title;
    }

    public string Artist { get; private set; }
    public string Title { get; private set; }

    public  IEnumerable<Track> Tracks
    {
        get { return _tracks; }
    } 

    public void AddTrack(Track track)
    {
        _tracks.Add(track);
    }

    public CD WithTrack(string title, TimeSpan length)
    {
        AddTrack(new Track(Artist, title, length));
        return this;
    }
}

This is Value Object class - private setters does not allow to change property values outside of this class. And here is class for track:

public class Track
{
    public Track(string artist, string title, TimeSpan length)
    {
        Artist = artist;
        Title = title;
        Length = length;
    }

    public string Artist { get; set; }
    public string Title { get; private set; }
    public TimeSpan Length { get; private set; }
}

Now you can use Fluent API to create collection of CDs:

List<CD> cds = new List<CD>
    {
        new CD("Awake", "Dream Theater")
            .WithTrack("6:00", new TimeSpan(00, 05, 31))
            .WithTrack("Caught in a Web", new TimeSpan(00, 05, 28))
            .WithTrack("Innocence Faded", new TimeSpan(00, 05, 34)),
        new CD("Second cd", "TestArtist")
            .WithTrack("TrackForSecond", new TimeSpan(00, 13, 37))
    };

And here is XML creation:

var xDoc = new XDocument(
    new XDeclaration("1.0", "utf-8", "yes"),
    new XElement("cds",
          from cd in cds
          orderby cd.Artist
          select new XElement("cd",
               new XAttribute("artist", cd.Artist),
               new XAttribute("name", cd.Title),
               from t in cd.Tracks
               select new XElement("track",
                     new XElement("artist", t.Artist),
                     new XElement("title", t.Title),
                     new XElement("length", t.Length)));

You had several problems here - missing root node, and enumerating over all CDs on each iteration.

like image 90
Sergey Berezovskiy Avatar answered Sep 22 '22 08:09

Sergey Berezovskiy


There are a few problems with your XDocument construction.

  1. There must be exactly one root element in an XDocument. Your statements are constructing a root element for each CD.
  2. You have strange nested loops in your LINQ. First you're ordering the CDs by artist, and then you're iterating again over the entire CD collection when producing the artist and name attributes. You want to produce these attributes from the "current" CD.
  3. You're using "c1" and "t1" in your LINQ instead of the iteration variables "cl" and "t".

Try this (forgive me for turning your getters/setters into properties:

var xmlOutput = new XDocument(
    new XDeclaration("1.0", "utf-8", "yes"),
    new XElement(
        "cds",
        from cd in cds
        orderby cd.Artist.ToUpperInvariant()
        select new XElement(
            "cd",
            new XAttribute("title", cd.Title),
            new XAttribute("artist", cd.Artist),
            new XElement(
                "tracks",
                from t in cd.Tracks
                select new XElement(
                    "track",
                    new XAttribute("artist", t.Artist),
                    new XAttribute("title", t.Title),
                    new XAttribute("length", t.Length))))));
like image 38
Michael Gunter Avatar answered Sep 24 '22 08:09

Michael Gunter