| 0 comments ]

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:







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;
}

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

XElement customerElement = data.Descendants("Customer").Where(c =&gt; 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">
</asp:textbox></td>             </tr>
<tr>                 <th>
Surname</th>                 <td>
<asp:textbox id="txtSurname" runat="server">
</asp:textbox></td>             </tr>
<tr>                 <th>
DOB</th>                 <td>
<asp:textbox id="txtDOB" runat="server">
</asp:textbox></td>             </tr>
<tr>                 <th>
Location</th>                 <td>
<asp:textbox id="txtLocation" runat="server">
</asp:textbox></td>             </tr>
</tbody>         <tfoot>
<tr>                 <td colspan="2">
<asp:button id="btnAddCustomer" onclick="btnAddCustomer_Click" runat="server" text="Add Customer">
</asp:button></td>             </tr>
</tfoot>     </table>
</div>
<div>
<asp:gridview autogeneratecolumns="false" autogeneratedeletebutton="true" autogenerateeditbutton="true" datakeynames="ID" datasourceid="odsCustomers" id="gvCustomers" runat="server">
<columns>
<asp:boundfield datafield="Forename" headertext="Forename">
<asp:boundfield datafield="Surname" headertext="Surname">
<asp:boundfield datafield="DOB" headertext="DOB">
<asp:boundfield datafield="Location" headertext="Location">
</asp:boundfield></asp:boundfield></asp:boundfield></asp:boundfield></columns>
</asp:gridview>

<asp:objectdatasource dataobjecttypename="LinqToXml.Customer" deletemethod="Delete" id="odsCustomers" runat="server" selectmethod="GetCustomers" typename="LinqToXml.Data.DAL" updatemethod="Save">
</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:

0 comments

Post a Comment