SQL Server knowledge center

everything about SQL Server
See also: Other Geeks@INDC

Going deeper with LINQ to XML Operations

Going deeper with LINQ to XML Operations

By : Kasim Wirama, MCDBA, MVP SQL Server

This article will discuss about classes in LINQ for XML. What is goal behind the creation API (separate DLL) LINQ for XML.

At the beginning, I thought that LINQ for XML API is created for accessing XML content with LINQ way. Later, my thought is incorrect. In fact, LINQ for XML API is independent from LINQ for XML. It means, you use LINQ for XML API to managing XML content whether you query with LINQ or not. Creation of this API is based on W3C standard XML infoset (http://www.w3.org/TR/xml-infoset).

What is infoset actually? Infoset is a set of information that specifies the structures of well-formed XML. What is well-formed XML in my understanding? As far as I know, well-formed XML has opening and closing tag, and each element has opening and closing tag inside their element container.

Goal creation of this API is to provide object oriented way of working with XML rather than document manipulation way with DOM. As element(s) has siblings and parents, it becomes good candidate for LINQ to come into XML stuff. Classes under this API are prefixed with X.

This API exists in System.Xml.Linq assembly with same namespace, so you need to add it as reference in your solution.

It seems there are some new ways of expressing LINQ to XML.

My Exploration

You can generate your XML into memory dynamically or loading from XML file, as sample below shown

XElement element = new XElement("Order",
new XElement("orderid","001"),
new XElement("quantity",10),
new XElement("price",50)
);

It will display

<Order>
<orderid>001</orderid>
<quantity>10</quantity>
<price>50</price>
</Order>

 

You can also calculate value among elements, for example calculating total amount in one order as below :

decimal totalPrice = (int)element.Element("quantity") * (decimal)element.Element("price");

It returns result 500

You need to implement namespace into XML. Namespace is useful in various scenario, one of them is to validate XML content against XML schema. XML namespace is declared in XNamespace class, as below :

XNamespace ns = "http://www.test.com/Customers";

And put namespace inside the XML element content like below :

XElement customers = new XElement (
ns+"customers",
new XElement(ns + "customer",
new XAttribute("firstname", "john"),
new XAttribute("lastname", "grisham")
),
new XElement(ns + "customer",
new XAttribute("firstname", "alex"),
new XAttribute("lastname", "good")
)
);

 

It will display output :

<customers xmlns="http://www.test.com/Customers">
<customer firstname="john" lastname="grisham" />
<customer firstname="alex" lastname="good" />
</customers>

 

You can add alias for XML namespace to the content.

XElement customers = new XElement (
ns+"customers",
new XAttribute(XNamespace.Xmlns+"c" , ns),
new XElement(ns + "customer",
new XAttribute("firstname", "john"),
new XAttribute("lastname", "grisham")),
new XElement(ns + "customer",
new XAttribute("firstname", "alex"),
new XAttribute("lastname", "good")
)
);

It will display the following result :

<c:customers xmlns:c="http://www.test.com/Customers">
<c:customer firstname="john" lastname="grisham" />
<c:customer firstname="alex" lastname="good" />
</c:customers>

 

Other useful class is Annotation. For XML, annotation is more like attribute in a class.
You can create a class as below :

public class CustomerNote
{
public string Notes;
}

Then you can create instance of CustomerNote and add it to XElement instance, later then you can retrieve back the annotation object and extract its property to display to the screen, see below code:

CustomerNote note = new CustomerNote { Notes = "just dummy notes" };
customers.AddAnnotation(note);
///retrieve back the object and extract value from its property
Console.WriteLine(customers.Annotation<CustomerNote>().Notes);

You can change XML element for example replace a element with its content, or change its element/attribute value with method ReplaceWith, SetAttributeValue, and SetElementValue. Feel free to see the content after change with Console.WriteLine(customers); here is the sample of ReplaceWith method

Sample of ReplaceWith method :

customers.LastNode.ReplaceWith(new XElement(ns+"customer",
new XAttribute("firstname","alexander"),
new XAttribute("lastname","good"),"Alexander"));

You can output the result into XML form from collection type object, for example :

public enum Countries{
USA,
Indonesia
}

public class Customer
{
public Countries Country;
public string Name;
public string City;
}

 

Then in Main static method of client console program, you create array of customers:

Customer[] customers = {
new Customer{ City="Jakarta", Country=Countries.Indonesia, Name="Anton"},
new Customer{ City="Bali", Country=Countries.Indonesia, Name="Budi"},
new Customer{ City="Wichita", Country=Countries.USA, Name="Inga"},
new Customer{ City="New York", Country=Countries.USA, Name="John"},
new Customer{ City="Florence", Country=Countries.USA, Name="George"}
};

var result =
new XElement("customers",
from c in customers
where c.Country == Countries.Indonesia
select new XElement(
new XElement("customer"
, new XAttribute("city",c.City)
, c.Name
)
)
);

 

The output is :

<customers>
<customer city="Jakarta">Anton</customer>
<customer city="Bali">Budi</customer>
</customers>

You can also query from xml file with LINQ, here the XML content structure :

<?xml version="1.0" encoding="utf-8" ?>
<customers>
<customer name="..." city="..." country="...." />
<customer name="..." city="..." country="...." />
.........
</customers>

XElement element = XElement.Load(@"c:\mycustomer.xml");

var result = from item in element.Elements("customer")
                where (string)item.Attribute("country") == "Indonesia"
                orderby (string)item.Attribute("name")
                select new
                {
                                Name = (string)item.Attribute("name"),
                                City = (string)item.Attribute("city") 
                };

The return type is IEnumerable<XElement>

You can also do querying between XML and object like this query below :

var result = from item in element.Elements("customer")
join o in orders
on (string)item.Attribute("Name") equals o.CustomerName
orderby (string)item.Attribute("Name")
select new
{
Name = (string)item.Attribute("Name"),
City = (string)item.Attribute("City"),
IdProduct = o.IdProduct,
Quantity = o.Quantity
};

Before you try above example, try to make dummy class Orders and array instance of order like this code below :

public class Order
{
public int OrderID;
public string CustomerName;
public int Quantity;
public int IdProduct;
}

And this one:

Order[] orders = {
new Order{ IdProduct=1, CustomerName="....", OrderID = 1, Quantity=10},
new Order{ IdProduct=2, CustomerName="....", OrderID = 2, Quantity=20}
};

You can replace repetitive explicit casting as you can see in (string)item.Attribute("Name") and (string)item.Attribute("City") with alias for each of them, so the query can be written as below:

var result = from item in element.Elements("customer")
let xName = (string)item.Attribute("name")
let xCity = (string)item.Attribute("city")
join o in orders
on xName equals o.CustomerName
orderby xName
select new
{
Name = xName,
City = xCity,
IdProduct = o.IdProduct,
Quantity = o.Quantity
};

In XML transformation with XSL, LINQ to XML also work with System.Xml.Xsl.XslCompiledTranform class to do transformation. XslCompiledTransform requires XPathNavigator class that exists in System.Xml.XPath. You can get the navigator instance from extension method CreateNavigator either from XmlDocument, XDocument or XElement. Here is the example.

XElement customers = XElement.Load(@"c:\mycustomer.xml");
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(@"c:\myxslt.xslt");xslt.Transform(customers.CreateNavigator(), null, Console.Out);

You can create XSLT from any XML tool including Visual Studio. With XPath, you can also do filtering with extension method XPathEvaluate.

For example, filtering only customer from Indonesia.

XElement result = new XElement(
"customers",
from c in customers
select new XElement
(
"customer",
new XAttribute("name", c.Name),
new XAttribute("city", c.City),
new XAttribute("country", c.Country)
)
);

Console.WriteLine(result.ToString());
Console.WriteLine();

var filter = (IEnumerable<object>)result.XPathEvaluate("/customer[@country='Italy']/@name");
foreach (var item in filter)
{
Console.WriteLine(item);
}

Because XPathEvaluate return object, you need to do explicit cast to IEnumerable<object>.

Other method is XPathSelectElement and XPathSelectElements, XPathSelectElement only return XElement type, whereas XPathSelectElements return IEnumerable<XElement>, so you can filter on XML nodes that returning IEnumerable<XElement> and you can join to IEnumerable<object> to return desired result. Here is the example :

XElement customers = XElement.Load(@"c:\mycustomer.xml");
var result = from c in customers.XPathSelectElements("/customer[@country='Indonesia']")
                let xName = (string)c.Attribute("name")
                let xCity = (string)c.Attribute("city")
                let xCountry = (string)c.Attribute("country")
                join o in orders
                on xName equals o.CustomerName
                orderby xName
                select new
                {
                                Name = xName,
                                City = xCity,
                                Country = xCountry,
                                IdProduct = o.IdProduct,
                                Quantity = o.Quantity
                };

foreach (var item in result)
{
                Console.WriteLine(item);
}

Share this post: | | | |
Posted: Nov 30 2007, 12:57 PM by Kasim.Wirama | with 2 comment(s)
Filed under:

Comments

michaelrawi said:

Hmm, tulisannya perlu dirapikan deh pak. Saya ga ngerti bacanya :)

# December 4, 2007 12:20 PM

Kasim.Wirama said:

sudah saya coba rapikan, tetap saja berantakan, untuk sementara ini bisa copy paste ke dalam visual studio 2008.

# December 5, 2007 4:49 AM