Thursday, 27 December 2012

Linq To XML Best Practices

Download The Source Here

Introduction

    XML means Extended Markup Language, which is universal format and can be accepted by any programming language more readable and understandable markup so far by humans as well as machines. We will see how to use and work with XML in our daily routine with C#.Net.

For more information about XML you can find here


Syntax:

    XML follows tree like structure. Which divides into branches of same structure at same level. You can see the structure below.

<Root>
  <Child1>
    <Leaf1></Leaf1>
    <Leaf2></Leaf2>
                 -
                 -
    <Leafn></Leafn>
  </Child1>
  <Child2></Child2>

                 -
                 -
   <Chindn></Childn>

</Root>

Sample Employees XML Structure

XML Structure For Employees
XML Structure For Employees
The above structure can be  represented in xml as below.

  <Employees>
  <Employee>
    <Name></Name>
    <Address></Address>
    <Department></Department>
    <Salary></Salary>
    <Experience></Experience>
  </Employee>
  <Employee>
    <Name></Name>
    <Address></Address>
    <Department></Department>
    <Salary></Salary>
    <Experience></Experience>
  </Employee>
</Employees>

Different Methods To Handle XML with C#.NET

  1. Linq-To-XML
  2. XMLReader & XMLWriter
  3. XPath
Above are the different ways to deal with XML using c#.net. In this tutorial we will see the Linq-To-XML scenario which best and very much understandable by everybody easily.

Lets take a simple example employees and their personal information as shown in the XML structure below.


<Person>
  <Name></Name>
  <Address></Address>
  <Age></Age>
  <MaritualStatus></MaritualStatus>
  <Contacts>
    <Contact type="mobile/telephone/email"></Contact>
  </Contacts>
</Person>

Create a console application on visual studio 2010 and name it as XML-Best-Practices as shown below.

XML Best Practices Application
XML Best Practices Application
Modify your program.cs file as below to be able to run the application to choose among the three types of processing. And provide user the ability to select the type of operation

  1. AddNewPerson 
  2. AddPersons
  3. UpdatePerson
  4. DeletePerson
  5. SelectPersons
Linq To XML Console Operations
Linq To XML Console Operations

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XML_Best_Practices
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter your choice to proceed.\n1 -> Linq - To - XML\t2 -> XML Reader & XML Writer \t3 -> XPath ");
            int choice = int.Parse(Console.ReadLine());
            XMLProcessor xpPersons = null;
            if (choice == 1)
                xpPersons = new LinqToXML();
            else if (choice == 2)
                xpPersons = new XMLReaderAndWriter();
            else if (choice == 3)
                xpPersons = new XPathManager();
            string finalchoice = string.Empty;
            int personnum = 0;
            Random rnd = new Random();
            string[] MaritialStatus = new string[] { "Single", "Married", "Widowed" };
            string[] ContactTypes = new string[] { ContactType.Email.ToString(), ContactType.MobilePhone.ToString(), ContactType.TelePhone.ToString() };
            do
            {
                string operation = string.Empty;
                Console.WriteLine("Enter your choice to proceed\n 1 -> Add Person\t2 ->  Add Persons\t3 -> Update Person\t4 -> Delete Person\t5 -> Select Person(s)");
                operation = Console.ReadLine();
                switch (operation)
                {
                    case "1":
                        personnum++;
                        Person person = new Person();
                        person.Name = "Person" + (personnum);
                        person.Address = "Address " + personnum;
                        person.Age = rnd.Next(10, 50);
                        person.MaritialStatus = MaritialStatus[rnd.Next(100, 10000) % 3];
                        int noofcontacts = rnd.Next(1, 5);
                        person.Contacts = new List<Contact>();
                        for (int i = 0; i < noofcontacts; i++)
                        {
                            int j = rnd.Next(10, 999) % 3;
                            person.Contacts.Add(new Contact { ContactType = ContactTypes[j], ContactValue = "Contact" + ContactTypes[j] });
                        }
                        xpPersons.AddNewPerson(person);
                        break;
                    case "2":
                        Console.WriteLine("How many persons do you want to add?");
                        int noofpersons = int.Parse(Console.ReadLine());
                        List<Person> persons = new List<Person>();
                        for (int i = 0; i < noofpersons; i++)
                        {
                            personnum++;
                            person = new Person();
                            person.Name = "Person" + (personnum);
                            person.Address = "Address " + personnum;
                            person.Age = rnd.Next(10, 50);
                            person.MaritialStatus = MaritialStatus[rnd.Next(100, 10000) % 3];
                            noofcontacts = rnd.Next(1, 5);
                            person.Contacts = new List<Contact>();
                            for (int j = 0; j < noofcontacts; j++)
                            {
                                int k = rnd.Next(10, 999) % 3;
                                person.Contacts.Add(new Contact { ContactType = ContactTypes[k], ContactValue = "Contact" + ContactTypes[k] });
                            }
                            persons.Add(person);
                        }
                        xpPersons.AddPersons(persons);
                        break;
                    case "3":
                        persons = xpPersons.GetAllPersons();
                        persons.ForEach(p => Console.WriteLine(p.Name + "\n"));
                        Console.WriteLine("The person name to update : ");
                        string name = Console.ReadLine();
                        personnum++;
                        person = new Person();
                        person.Name = name;
                        person.Address = "Address " + personnum;
                        person.Age = rnd.Next(10, 50);
                        person.MaritialStatus = MaritialStatus[rnd.Next(100, 10000) % 3];
                        noofcontacts = rnd.Next(1, 5);
                        person.Contacts = new List<Contact>();
                        for (int i = 0; i < noofcontacts; i++)
                        {
                            int j = rnd.Next(10, 999) % 3;
                            person.Contacts.Add(new Contact { ContactType = ContactTypes[j], ContactValue = "Contact" + ContactTypes[j] });
                        }
                        xpPersons.UpdatePerson(name, person);
                        break;
                    case "4":
                        persons = xpPersons.GetAllPersons();
                        persons.ForEach(p => Console.WriteLine(p.Name + "\n"));
                        Console.WriteLine("The Person name to delete : ");
                        name = Console.ReadLine();
                        xpPersons.DeletePerson(name);
                        break;
                    case "5":
                    default: break;
                }
                Console.WriteLine("To continue press Y/y or to exit press N/n");
                finalchoice = Console.ReadLine();
            } while (String.Equals(finalchoice, "y", StringComparison.OrdinalIgnoreCase));
            Console.WriteLine("Person(s) Added Successfully.");
        }
    }
}

Create an interface and name it as XMLProcessor as shown below


 public interface XMLProcessor
    {
        void AddNewPerson(Person Person);
        void AddPersons(List<Person> Persons);
        void DeletePerson(string PersonName);
        void UpdatePerson(string PersonName, Person Person);
        List<Person> GetPersons(string personname = null, string address = null, int age = 0, string maritialstatus = null);
    }

Now add three classes namely LinqToXML, XMLReaderAndWriter, XPathManager and derive them from the interface created in the previous step. And implement all the methods declared in interface which will make our job easy. Here we are using Factory design pattern. Becuase our operations are much similar for the three cases we mentioned above.

We need to have some business objects to implement this tutorial. Their structure is as follows.

public class Person
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string MaritialStatus { get; set; }
        public int Age { get; set; }
        public List<Contact> Contacts { get; set; }
    }
    public class Contact
    {
        public string ContactType { get; set; }
        public string ContactValue { get; set; }
    }
    public enum ContactType
    {
        Email,
        TelePhone,
        MobilePhone
    }  

Linq-To-XML

       Adding New Person

      We set our environment ready for our practice. Now start with adding a new person to the XML using Linq-To-XML declare a property XElement in your class LinqToXML and implement your AddNewPerson(Person Person) to add persons to the element. The code block will be like below

public class LinqToXML : XMLProcessor
    {
        public XElement PersonsXE;
        public void AddNewPerson(Person Person)
        {
            XElement elmPerson = new XElement("Person",
                 new XElement("Name", Person.Name),
            new XElement("Address", Person.Address),
            new XElement("MaritialStatus", Person.MaritialStatus),
            new XElement("Age", Person.Age)
                );
            XElement elmContact = new XElement("Contacts");
            foreach (Contact contact in Person.Contacts)
                elmContact.Add(new XElement("Contact", new XAttribute("Type", contact.ContactType), contact.ContactValue));
            elmPerson.Add(elmContact);
            if (PersonsXE == null)
                PersonsXE = new XElement("Persons");
            PersonsXE.Add(elmPerson);
        }
}

Here we are preparing the Person XElement based on the passed details and appending the new element to the main XML. Here we are checking if the main XML already exists or needs to create.

        Add Multiple Persons

We added one person, now the job is to add multiple persons to our XML using the Person class collection as declared in our Interface. Update our class LinqToXML by adding the method below.

public void AddPersons(List<Person> Persons)
        {
            if (Persons != null)
                Persons.ForEach(p => AddNewPerson(p));
        }

Here is our code snippet to add multiple persons.I called the previous method AddNewPerson() here, Because if I write the code to add each person here it will be much more similar to the existing code snippet.

        Update Person

 We implemented adding single or multiple persons to xml using Linq so far now we will see how to update an existing record. The method signature is as per the declaration on interface UpdatePerson(string name, Person person). This method accepts person name and Person Object. The Person object values will be updated to the person element in the main xml for the passed person name.


public void UpdatePerson(string PersonName, Person Person)
        {
            XElement elmPerson = (from p in PersonsXE.Descendants("Person") where p.Element("Name").Value == PersonName select p).FirstOrDefault();
            if (elmPerson != null)
            {
                elmPerson.Descendants("Contact").Remove();
                elmPerson.SetElementValue("Address", Person.Address);
                elmPerson.SetElementValue("MaritialStatus", Person.MaritialStatus);
                elmPerson.SetElementValue("Age", Person.Age);
                foreach (Contact contact in Person.Contacts)
                    elmPerson.Element("Contacts").Add(new XElement("Contact", new XAttribute("Type", contact.ContactType), contact.ContactValue));
                Console.WriteLine("The person " + PersonName + " updated successfully.");
            }
            else
                Console.WriteLine("The person " + PersonName + " does not exists.");
        }

Here the beauty of SetElementValue(<element name>,<element value>) is if the element already exists it will replace the value otherwise it will create the element and sets the passed value.

Delete Person

We have a final operation for data manipulation that is delete how we can do this is just pass the person name to delete and execute the DeletePerson(string personname). Which will find for the element with the passed name and removes it from the main XML. The code snippet is as below.


public void DeletePerson(string PersonName)
        {
            XElement PersonToDelete = (from p in PersonsXE.Descendants("Person") where p.Element("Name").Value == PersonName select p).FirstOrDefault();
            if (PersonToDelete != null)
            {
                PersonToDelete.Remove();
                Console.WriteLine("The person " + PersonName + " deleted successfully.");
            }
            else
            {
                Console.WriteLine("The person " + PersonName + " does not exists.");
            }
        }
Linq To XML Basic Operations View
Linq To XML Basic Operations View
This is how we do for all our basic operation using Linq To XML which is very simple and user friendly. Now we will see the fetching the person records from main XML for different Scenarios.

Select Persons

   This is the final method we are going to check is  GetPersons() for different parameters passed. Here we are going to use the ms.net c#4.0 feature optional parameters. So we can define default values for the parameters on method signature. If we dont pass any value for a parameter the default value will be taken.
So we can pass none or any number or parameters for this method. We will see how it works with example.


public List<Person> GetPersons(string personname = null, string address = null, int age = 0, string maritialstatus = null)
        {
            List<Person> Persons = new List<Person>();
            if (PersonsXE != null && PersonsXE.Descendants("Person").Count() > 0)
            {
                Persons = (from p in PersonsXE.Descendants("Person")

                           where
                            (string.IsNullOrEmpty(personname) || p.Element("Name").Value == personname) &&
                            (string.IsNullOrEmpty(address) || p.Element("Address").Value.Contains(address)) &&
                            (age == 0 || p.Element("Age").Value == age.ToString()) &&
                            (string.IsNullOrEmpty(maritialstatus) || p.Element("MaritialStatus").Value == maritialstatus)
                           select
                               new Person
                               {
                                   Name = p.Element("Name").Value,
                                   Address = p.Element("Address").Value,
                                   MaritialStatus = p.Element("MaritialStatus").Value,
                                   Age = int.Parse(p.Element("Age").Value),
                                   Contacts = (from c in p.Descendants("Contact")
                                               select new Contact
                                               {
                                                   ContactType = c.Attribute("Type").Value,
                                                   ContactValue = c.Value
                                               }
                                               ).ToList()
                               }).ToList();
            }
            return Persons;
        }

Output

Final Linq To XML Select Results
Final Linq To XML Select Results
This way we can get more filtered results if we can apply more. The Linq to XML is pretty much simple and very easy to develop and even fast.

Download The Source Here