Linq to XML Tutorial

34

Posted by Joe | Posted in ASP.NET, C#, Linq, XML | Posted on 08-01-2010

Download source

This is an introduction to Linq to XML showing how to read, insert, update and delete from an XML file.

First of all lets look at the XML file I will be using:

<?xml version="1.0" encoding="utf-8"?>
<Customers>
 <Customer ID="1">
  <Forename>Joe</Forename>
  <Surname>Stevens</Surname>
  <DOB>31/01/1983</DOB>
  <Location>Sydney</Location>
 </Customer>
 <Customer ID="2">
  <Forename>Tom</Forename>
  <Surname>Male</Surname>
  <DOB>02/02/1977</DOB>
  <Location>Brisbane</Location>
 </Customer>
 <Customer ID="3">
  <Forename>Emily </Forename>
  <Surname>Stevens</Surname>
  <DOB>14/01/1988</DOB>
  <Location>Sydney</Location>
 </Customer>
 <Customer ID="4">
  <Forename>Lee</Forename>
  <Surname>Phipps</Surname>
  <DOB>05/12/1982</DOB>
  <Location>Melbourne</Location>
 </Customer>
 <Customer ID="5">
  <Forename>Saul</Forename>
  <Surname>Stevens</Surname>
  <DOB>02/08/1984</DOB>
  <Location>Perth</Location>
 </Customer>
</Customers>

As you can see it’s a very simple list of customers.  In my project I have also created a Customer class to represent each customer:

public class Customer
{
    public int ID { get; set; }
    public string Forename { get; set; }
    public string Surname { get; set; }
    public string DOB { get; set; }
    public string Location { get; set; }
}

Firsly I want to be able to select a single customer based on their ID.  The following method shows how to load the XML file and query it to find the customer we want, and return the data as a single Customer object:


public static Customer GetCustomer(int customerID)
{
    XDocument data = XDocument.Load(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));

    return (from c in data.Descendants("Customer")
            where c.Attribute("ID").Value.Equals(customerID.ToString())
            select new Customer()
            {
                ID = Convert.ToInt32(c.Attribute("ID").Value),
                Forename = c.Element("Forename").Value,
                Surname = c.Element("Surname").Value,
                DOB = c.Element("DOB").Value,
                Location = c.Element("Location").Value

            }).FirstOrDefault();
}

Firstly the XML document is loaded using the XDocument class.  I then use Linq to look at all the Customer elements and find the one where the ID attribute matches the value passed to the method.  The Customer object is then populated with each element within the Customer element.

I’ve created a simple web form that uses an ObjectDataSource with this method to display a customer based on the ID in the query string:


<asp:DetailsView
    ID="dvCustomer"
    DataSourceID="odsCustomer"
    runat="server">
</asp:DetailsView>

<asp:ObjectDataSource
    ID="odsCustomer"
    TypeName="LinqToXml.Data.DAL"
    SelectMethod="GetCustomer"
    runat="server">
    <SelectParameters>
        <asp:QueryStringParameter Name="customerID" QueryStringField="id" DefaultValue="1" />
    </SelectParameters>
</asp:ObjectDataSource>

The page creates the following table:

linqtoxml_single

To get a list of all customers is very similar to getting a single customer, although the method will return a generic list of Customer objects:


public static List<Customer> GetCustomers()
{
    XDocument data = XDocument.Load(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));

    return (from c in data.Descendants("Customer")
            orderby c.Attribute("Surname")
            select new Customer()
            {
                ID = Convert.ToInt32(c.Attribute("ID").Value),
                Forename = c.Element("Forename").Value,
                Surname = c.Element("Surname").Value,
                DOB = c.Element("DOB").Value,
                Location = c.Element("Location").Value

            }).ToList();
}

Here I am also using the orderby operator to order the results by the value of the Surname element.

I have a single method called Save which is used for both inserting and updating.  It accepts a Customer object and performs an insert or update depending on the ID value of that object:


public static void Save(Customer customer)
{
    XDocument data = XDocument.Load(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));

    if (customer.ID > 0)
    {
        XElement customerElement = data.Descendants("Customer").Where(c => c.Attribute("ID").Value.Equals(customer.ID.ToString())).FirstOrDefault();
        if (customerElement != null)
        {
            customerElement.SetElementValue("Forename", customer.Forename);
            customerElement.SetElementValue("Surname", customer.Surname);
            customerElement.SetElementValue("DOB", customer.DOB);
            customerElement.SetElementValue("Location", customer.Location);

            data.Save(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));
        }
    }
    else
    {
        XElement newCustomer = new XElement (   "Customer",
                                                new XElement("Forename", customer.Forename),
                                                new XElement("Surname", customer.Surname),
                                                new XElement("DOB", customer.DOB),
                                                new XElement("Location", customer.Location)
                                            );

        newCustomer.SetAttributeValue("ID", GetNextAvailableID());

        data.Element("Customers").Add(newCustomer);
        data.Save(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));
    }
}

In this method I load the XML document as before and then check the value of customer.ID to see whether to insert or update.

If the value is greater than zero then it is an update.  Firstly I get the element matching the ID using the Where extension method and a lambda expression using the given ID. If the element is found (not null), I use the SetElementValue method to update the values of the child elements with their new values.  After this is done I call save on the XDocument and pass the URL on the original XML file to update it.

If customer.ID is zero then I know this is a new record and I need to perform an insert.  This is done by creating a new XElement with the name Customer, then by creating a list of XElements for the children.  I then set the ID attribute on the Customer element by calling a method called GetNextAvailableID which I’ll explain in a moment.  Now my element is ready to be inserted so using my XDocument I use the Element method to get the Customers element and the Add method to add my new element to it.  As with the update I then need to call the save method on the XDocument.

The GetNextAvailableID method simply finds the highest ID used in the XML document and adds one to it:


private static int GetNextAvailableID()
{
    XDocument data = XDocument.Load(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));

    return Convert.ToInt32(
            (from c in data.Descendants("Customer")
             orderby Convert.ToInt32(c.Attribute("ID").Value) descending
             select c.Attribute("ID").Value).FirstOrDefault()
            ) + 1;
}

As you can see I’m using Linq to query the XML sorting the result by ID in decending order then selecting the first item; this will be the one with the highest ID. I then add one to that ID and return the value.

The final part of the tutorial is deleting from the XML file:


public static void Delete(Customer customer)
{
    XDocument data = XDocument.Load(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));

    XElement customerElement = data.Descendants("Customer").Where(c => c.Attribute("ID").Value.Equals(customer.ID.ToString())).FirstOrDefault();
    if (customerElement != null)
    {
        customerElement.Remove();
        data.Save(HttpContext.Current.Server.MapPath("~/Data/Customers.xml"));
    }
}

As with the update code I’m selecting the element based on the customer ID.  Once I have that element all I need to do is call the Remove method on it, then save the XDocument.

I have put all this code to use with a GridView and ObjectDataSource in the following form:

<div>
    <table>
        <tbody>
            <tr>
                <th>
                    Forename
                </th>
                <td>
                    <asp:TextBox ID="txtForename" runat="server" />
                </td>
            </tr>
            <tr>
                <th>
                    Surname
                </th>
                <td>
                    <asp:TextBox ID="txtSurname" runat="server" />
                </td>
            </tr>
            <tr>
                <th>
                    DOB
                </th>
                <td>
                    <asp:TextBox ID="txtDOB" runat="server" />
                </td>
            </tr>
            <tr>
                <th>
                    Location
                </th>
                <td>
                    <asp:TextBox ID="txtLocation" runat="server" />
                </td>
            </tr>
        </tbody>
        <tfoot>
            <tr>
                <td colspan="2">
                    <asp:Button ID="btnAddCustomer" Text="Add Customer" OnClick="btnAddCustomer_Click" runat="server" />
                </td>
            </tr>
        </tfoot>
    </table>
</div>
<div>
    <asp:GridView
        ID="gvCustomers"
        DataSourceID="odsCustomers"
        AutoGenerateColumns="false"
        AutoGenerateEditButton="true"
        AutoGenerateDeleteButton="true"
        DataKeyNames="ID"
        runat="server">
        <Columns>
            <asp:BoundField HeaderText="Forename" DataField="Forename" />
            <asp:BoundField HeaderText="Surname" DataField="Surname" />
            <asp:BoundField HeaderText="DOB" DataField="DOB" />
            <asp:BoundField HeaderText="Location" DataField="Location" />
        </Columns>
    </asp:GridView>

    <asp:ObjectDataSource
        ID="odsCustomers"
        TypeName="LinqToXml.Data.DAL"
        SelectMethod="GetCustomers"
        DataObjectTypeName="LinqToXml.Customer"
        UpdateMethod="Save"
        DeleteMethod="Delete"
        runat="server">
    </asp:ObjectDataSource>
</div>

The only code required on the web form is the code to add a new customer, as the selecting, updating and deleting is handled by the ObjectDataSource by calling the methods discussed above:


protected void btnAddCustomer_Click(object sender, EventArgs e)
{
    Customer customer = new Customer()
    {
        Forename = txtForename.Text.Trim(),
        Surname = txtSurname.Text.Trim(),
        DOB = txtDOB.Text.Trim(),
        Location = txtLocation.Text.Trim()
    };

    Data.DAL.Save(customer);

    ClearForm();
    gvCustomers.DataBind();
    txtForename.Focus();
}

private void ClearForm()
{
    txtForename.Text = string.Empty;
    txtSurname.Text = string.Empty;
    txtDOB.Text = string.Empty;
    txtLocation.Text = string.Empty;
}

My resulting form looks like this:

linqtoxml_multi

Download source

Post to Twitter Post to Delicious Post to Digg Post to Facebook Post to Reddit

Comments (34)

Great article, just what I needed to get me started on Linq To XML. Thanks

Hi Hugo

Glad you found it useful.

Joe

PERFECT !!! THANKS !!

Its nice article to start LINQ to XML.
I found it is helpful to understand the Basic Xml operation using LINQ.

Hi Rajiv

Glad it was helpful

Its nice article to start LINQ to XML.
I found it is helpful to understand the Basic Xml operation using LINQ.

A Very Good Article :) Thanks A Lot For Sharing.

Hello, thank you for posting a comprehensive article and the source code as well.
However, i am facing a small problem, I created a new project based entirely on the source code you supply but when I run the application I get a “Object reference not set to an instance of an object.” exception when I attempt to get a list of all contacts.

Please assist

Thank You

Hi Ralph

I can’t really give you an answer without know a bit more, or seeing the solution. Maybe upload your solution somewhere and I’ll have a look for you.

Cheers
Joe

Hi Joe

It is basically the same code as yours, but was trying to understand it by building from scratch – are you using Reflection for the Customer class?

Ralph

It’s just a POCO. I can’t really help without knowing exactly where you are getting your exception or seeing the source code.

Joe

Great example!! Thanks for sharing!! I wonder, that there is nothing comparable out there.
I found very strange to see a copy of your article.
Community means sharing and make it even better, not plain copying…….

Great Article…. Thanks Joe.

Hi,

Nice article, I am doing similar thing but i want to select the Phone based on Type (attribute)

some thing like this

XXX-XXX-XXXX
XXX-XXX-XXXX

So i can do the if inside the Select new Customer()
{
PhoneHome = ????
}

can you help me on that? Thanks

Superab!!!

It has made my job very easy to get started on Linq to xml.

Thank you

Hi i just used your class GetNextAvailableID() and when id reached 10, next avalible id was 2, i just delete FirstOrDefault from:
select c.Attribute(“ID”).Value).FirstOrDefault()
and it works fine, anyway this article help me a lot:) (I’m newbie to linqtoxml and c#)

Hi Joe,

Thank you for this article. It was very helpful for me.

Regards,
Kas

Thanks. This was a great help.

What would the save method look like, if each customer had a repeating sub element?

<phones>
<phone>555-1212</phone>
<phone>555-1111</phone>
<phone>555-2121</phone>
</phones>

Is there a bug in the GetCustomers() method in the orderby clause? I reckon that “Surname” is an Element not an Attribute and I also reckon that you would have to tack a .Value onto the end.

i.e. I think the orderby clause should be:
orderby c.Element(“Surname”).Value

Excellent!
Thank you.

Good article to begin with.

Thank !

Hi Joe,

It was a nice post. I am new to Linq.
However while tying your given code in POC (VS2010); I was geting null exception on below lines:
c.Attribute(“ID”)Value.Equals(customerID.ToString())
and
c.Element(“Forename”).Value
also on all similar lines.

While debugging I was able to rectify the issue by replcaing it to:
c.Element(“Customer”).Attribute(“ID”).Value.Equals(customerID.ToString())
and
c.Element(“Customer”).Element(“Forename”).Value

I was confused to see so many comments on the post and none of us faced such issue.

You can mail me for further clarification/information.

Please correct me know in case I have missed out anything or interpreted wrongly.

Regards,
Pranshu

Hi Pranshu

It’s difficult to say what your issue is without seeing all your code.

Cheers
Joe

Useful article! You deserve lot of thanks for your spending time on helping us learn the basics of LINQ to XML. This article is a nice introduction to those who want to start learning LINQ to XML.

It ws very helpful tutorial…..
learned a lot 4m examples

Great Article for beginner….

Awesome tutorial/example :)
thanks a lot

Thanks Joe, After several hours and some ugly code that only marginally worked this article cleared it all up for me. Keep it up.

Thanks for this post. Helped me a lot

thanks a lots.good article for beginners.

Thank you for taking the time to write such a clear and concise article on Linq to XML for the benefit of all.

GOOD TUTORIAL FOR BEGINNERS…THANKS A LOT

It is good.
I am getting error, while updating the thing, data in xml file is getting updated but while fetching it again I am getting “Object reference not set to null”. Can anyone help?

Snehal

Are you using the exact code from the post? Would need to see what line your getting the error on to give any help. Also, am assuming the error is ‘Object reference not set to an instance of an object”?

It’s possible that the element your trying to access doesn’t exist.

Joe

Write a comment