Showing posts with label LINQ. Show all posts
Showing posts with label LINQ. Show all posts

LINQ to XML ― brings new reasons to use more XML

I recently did a small talk about the benefits of using new XML API “LINQ to XML”. According to MSDN:

LINQ to XML provides an in-memory XML programming interface that leverages the .NET Language-Integrated Query (LINQ) Framework. LINQ to XML uses the latest .NET Framework language capabilities and is comparable to an updated, redesigned Document Object Model (DOM) XML programming interface.
The talk wealso requirednt very well, and luckily I managed to get the attention of the audience, because the approach I adopted was a little different than the normal. Instead of talking plainly about the new functions and properties, I rather tried to draw a comparison between the way we deal with the XML using existing and new API. I also shared why VB developers are more excited about this API than C# guys, and what is making them feel more privileged.

The core functionality of the new API revolves around 3 key concepts.

  1. Functional Construction
  2. Context-Free XML creation
  3. Simplified NameSpaces

Functional Construction:

The ability to create the entire XML tree or part of it by just using one statement. If you are someone like me who doesn’t play with XML day-in and day-out, you have to probably recall for a second how do you create XML and using which API. This is so true because the depth and breadth of XML API choices available to us today is overwhelming. For example,

  • XMLTextReader: for low-level parsing of XML documents.
  • XMLTextWriter: fast, non-cached, forward-only way of generating XML
  • XMLReader: read-only, forward-only API generally used to deal with large XML documents.
  • XMLDocument, XMLNode and XPathNavigator etc. etc.

So, if I want to create the below book XML in my application, I can either use XmlTextWriter.WriteStartElement() or I can also use XMLDocument.CreateNode() If document manipulation is also required.



<books>

<book>

<title>Essential .NET</title>

<author>Don Box</author>


<author>Chris Sells</author>

<publisher>Addison-Wesley</publisher>

</book>

</books>

Though, there is nothing wrong with both of the approaches mentioned above except the fact that they take more lines of code and more time as well just to churn out a tiny piece of XML. LINQ to XML aims to solve this problem by introducing XElement which takes params array as a parameter in one of its constructors allowing us to write entire XML tree in one statement.



XElement elements = new XElement("books",

new XElement("book",
new XElement("title", "Essential .NET"),
new XElement("author", "Don Box"),
new XElement("author", "Chris Sells"),
new XElement-W("publisher", "Addisonesley")
)
);

Context-Free XML creation:

When creating XML using DOM, everything has to be in context of parent document. This document-centric approach for creating XML results in code hard to read, write and debug. In LINQ to XML, attributes have been given first-class status. So, rather than going through factory methods to create elements and attributes, we can use compositional constructors offered by XElement and XAttribute class.

If I want to add an ISBN number as an attribute to the book element in the above book XML, I can simply write:



XElement elements = new XElement("books",
new XElement("book", new XAttribute(“ISBN”, “0201734117”),
new XElement("title", "Essential .NET"),
new XElement("author", "Don Box"),
new XElement("author", "Chris Sells"),
new XElement("publisher", "Addison-Wesley")
)
);

Simplified Namespaces:

I believe this is the most confusing aspect of XML. With the existing set of API, we have to remember many things like XML names, NameSpaces, prefixes associated with the NameSpaces, Namespace managers etc. LINQ to XML allows us to forget everything else and just focus on one thing called “fully expanded name” which is represented by XName class.

Let’s see how this new functionality differs from the existing one by taking an example of RSS feed of my blog. In the RSS document, which can be accessed from here http://feeds.feedburner.com/feed-irfan (right click → view source), I am interested in “totalResults” element which is prefixed by “openSearch”. This is how I do it using XMLNameSpaceManager which has been part of the .NET framework for a long time.



XmlDocument rss = new XmlDocument();

rss.Load("http://feeds.feedburner.com/feed-irfan");

XmlNamespaceManager nsManager = new XmlNamespaceManager(rss.NameTable);

nsManager.AddNamespace("openSearch", "http://a9.com/-/spec/opensearchrss/1.0/");

XmlNodeList list = rss.SelectNodes("//openSearch:totalResults", nsManager);

foreach (XmlNode node in list)
{
Console.WriteLine(node.InnerXml);
Console.ReadLine();
}


You can see I have to create XMLNameSpaceManager, add a namespace, remember the syntax of the query, provide the manager as a parameter…huh...too much of work. LINQ to XML says, forget about XMLNameSpaceManager, and create a fully expanded name and use it every time.



XElement rss = XElement.Load("http://feeds.feedburner.com/feed-irfan");

XNamespace ns = "http://a9.com/-/spec/opensearchrss/1.0/";

IEnumerable<XElement> items = rss.Descendants(ns + "totalResults");

foreach (XElement element in items)
{
Console.WriteLine(element.Value);
Console.ReadLine();
}

We can also take a look at how exactly we can load, create and update XML using LINQ to XML API.

Loading XML


  • Loading from URL:
    XElement feed = XElement.Load("http://feeds.feedburner.com/feed-irfan");

  • Loading from file:
    XElement file = XElement.Load(@"book.xml");

  • Loading from String:
    XElement document = XElement.Parse("<books><book><title>Essential.NET</title><author>Don Box</author><author>Chris Sells</author><publisher>Addison-Wesley</publisher></book></books>");

  • Loading from a reader:
    using (XmlReader xReader = XmlReader.Create(@"book.xml"))
    {
    while (xReader.Read())
    {
    if (xReader.NodeType == XmlNodeType.Element)
    break;
    }
    XElement messages = (XElement)XNode.ReadFrom(xReader);
    Console.WriteLine(messages);
    Console.ReadLine();
    }

  • XDocument:
    You may wonder If for every kind of load we use XElement, what is then the purpose of XDocument then? XDocument can be used whenever we require additional details about the document e.g. document type definition(DTD), document declaration etc. These are details which XElement doesn’t seem to provide.

Creating XML

Functional construction key concept that I mentioned above, defines the way XML is created using LINQ to XML. We have also seen above how to create an XML tree with fully qualified names. We can now take a look at how to associate a prefix with a namespace while creating an XML document.

Associating prefixes is just a matter of creating an XAttribute with appropriate values in the constructor and supplying it to XElemennt prefix is going to be associated with.



XNamespace ns = "http://www.essential.net"

var xml2 = new XElement("books",
new XElement(ns + "book", new XAttribute(XNamespace.Xmlns + "pre", ns),
new XElement("title", "Essential .NET"),
new XElement("author", "Don Box"),
new XElement("publisher", "Addison-Wesley")
)
);

XML Literals

As I mentioned in the beginning of this post that there is something in this API exclusively for VB.NET 9.0(+) developers. It is a new offering called “XML Literal” that enables developers to embed XML directly within VB.NET code. We have seen how to create book XML using Functional Construction above. Let’s now see how the same can be done using XML Literal:



Dim bookXML As XElement = <books>
<book>
<title>Essential .NET</title>
<author>Don Box</author>
<author>Chris Sells</author>
<publisher>Addison-Wesley</publisher>
</book>
</books>

bookXML.Save("book.xml", SaveOptions.None)

Rather than creating LINQ to XML object hierarchies that represent XML, VB guys instead can define the entire XML using XML syntax. And, If they want to make it more dynamic, they can also use ASP.NET code nuggets (<%= %>) which is called “expression holes” to embed the dynamic values into XML Literals.



Private Sub GetBookXML(ByVal bookName As String, ByVal publisher As String, ByVal ParamArray authors As String())

Dim customAttrib = "ISBN"
Dim bookXML As XElement = <books>
<book <%= customAttrib %>=<%= "0201734117" %>>
<title><%= bookName %></title>
<author><%= authors(0) %></author>
<author><%= authors(1) %></author>
<publisher><%= publisher %></publisher>
</book>
</books>

bookXML.Save("book.xml", SaveOptions.None)

End Sub

XML Axis Properties

Another unique feature which is available only in VB.NET 9.0 is “XML Axis properties”, which allows XML axis methods to be called using more compact syntax. Let’s take a look at those properties


  1. Child Axis Property
    This property allows all the child elements to return with a particular name. For example, I am looking for <author> element in my book XML. Using Child Axis Property I can directly say:
    Dim authorName as String = bookXML.<book>.<author>(0).Value 

    And, If you are interested in all the authors:
    Dim authors As IEnumerable(Of XElement) = bookXML.<book>.<author>
    Dim authors As List(Of String) = (From author As XElement In authors _
    Select author.Value).ToList()

  2. Descendent Axis Property
    It returns all the decedent elements that have the qualified name that is specified within the angle brackets. To see how it works, we’ll use the XML we produced using GetBookXML() method in XML Literal section explained above as input.


    Dim elements As IEnumerable(Of XElement) = bookXML. . .<title>.Where(Function(t) CInt(t.@ISDN) > 1)
    For Each e As XElement In elements
    Console.WriteLine(e.Value)
    Next

  3. Attribute Axis Property
    This property returns the string value of the attribute that has the qualified name that is specified after the “@” character.
    We have already seen an example of this property in the previous “Descendent property” section where we tried to get all the book titles by providing their ISDN property values to the WHERE clause.
    Another example could be a tiny piece of code that returns all the ISDN number in the entire bookXML document that we saved earlier.

    Dim ISDNList As New List(Of String)
    Dim elements As IEnumerable(Of XElement) = bookXML. . .<title>

    For Each e As XElement In elements
    ISDNList.Add(e.@ISDN.Value)
    Next

XML axis properties help a great deal in searching in XML documents. By having this shorthand syntax for accessing the primary XML axes, Visual Basic developers can stay focused on the XML they are trying to consume. As I said earlier, for the developers who deal with XML everyday, learning and understanding XPath is not a problem. However, for those like me who use XML rarely, Axis properties being a no-brainer has more attraction.


HTH,

XML Literals - (VB.NET only)

After I learned about lambda statements that they are supported only in C# 3.5 and not in VB.NET 9.0, I got curious and thought why not to find something that is available only for VB.NET and not for C#, and found this interesting thing which is called XML Literals. Using this nice features which is a part of LINQ to XML API, we can embed XML directly within Visual Basic 9.0 code.

To illustrate its power let's have a look at the below XML that we will produce using XML Literal.


<books>
<book>
<title>LINQ IN ACTION</title>
<author>FABRICE MARGUERIE</author>
<author>STEVE EICHERT</author>
<author>JIM WOOLEY</author>
<publisher>Manning</publisher>
</book>
</books>

now take a look at Listing 1.1 below, which shows the code for creating the XML using the XML literal syntax offered by VB9.

Listing 1.1

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

Dim booksElement As XElement = <books>
<book>
<title>LINQ IN ACTION</title>
<author>FABRICE MARGUERIE</author>
<author>STEVE EICHERT</author>
<author>JIM WOOLEY</author>
<publisher>Manning</publisher>
</book>
</books>
End Sub

Note that in the above code we are using XElement which is a new class introduced in .NET 3.5 which represents an XML element. According to MSDN:
"XElement can be used to create elements; change the content of the element; add, change, or delete child elements; add attributes to an element; or serialize the contents of an element in text form"
The XML fragment in listing 1.1 above is static. When building real applications we might need to create XML using expressions stored in a set of local variables. XML Literal allows us to do so through expression holes which is expressed with the .

Listing 1.2

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

Dim xml = LoadXML("LINQ IN ACTION", "Manning", "FABRICE MARGUERIE", "STEVE EICHERT", "JIM WOOLEY")

End Sub

Private Sub LoadXML(ByVal title As String, ByVal publisher As String, ByVal ParamArray authours() As String)

Dim booksElement As XElement = <books>
<book>
<title><%= title %></title>
<author><%= authours(0) %></author>
<author><%= authours(1) %></author>
<author><%= authours(2) %></author>
<publisher><%= publisher %></publisher>
</book>
</books>

End Sub

XML Literals allows us to embed XML directly within the code without having to learn the details of XML API. It's a great addition to VB9.0 and hope that it will get added to C# as well in future.



HTH,

From Delegates to Lambda Expressions

Delegates:

Delegate was a wonderful addition to .NET 1.1 in early 2000. It is a type that can store a pointer to a function. The below snippet shows the way we would use delegates in our everyday programming.
Listing 1.1

delegate DataTable GetUserDetailsDelegate(int userID);

class Program
{
static void Main(string[] args)
{
GetUserDetailsDelegate GetUserDetails = new GetUserDetailsDelegate(GetUserDetailsByUserID);
DataTable dt = GetUserDetails(1);
}

private DataTable GetUserDetailsByUserID(int userID)
{
return new DataTable();
}
}

Anonymous Methods

C#2.0 was improved to allow working with delegates through anonymous methods. I said C#, because VB.NET doesn’t offer support for anonymous methods. These anonymous methods allow us to write shorter code and avoid the need for explicitly named methods. Let's modify the code in Listing1.1 and re-write it using anonymous methods.
Listing 1.2
delegate DataTable GetUserDetailsDelegate(int userID);

class Program
{
static void Main(string[] args)
{
GetUserDetailsDelegate GetUserDetails = delegate(int userID) { return new DataTable(); };
var dt = GetUserDetails(1);
}

Lambda Expressions

Now, Starting with C# 3.0, instead of anonymous methods we can use lambda expressions.
Listing 1.3
C#
var GetUserDetails = userID => { return new DataTable(); };
var dt = GetUserDetails(1);
VB.NET
Dim GetUserDetails = Function(x) New DataTable()
Dim dt = GetUserDetails(1)

The anonymous method introduced in C#2.0 is verbose and imperative in nature. In contrast, lambda expressions provide a more concise syntax, providing much of the expressive power of functional programming languages. It is a superset of anonymous methods with additional functionalities like inferring types, using both statement blocks and expression as bodies etc.

Note that, In the above lambda expression left hand side variable is of anonymous type. Anonymous type is a new language enhancement that enable us to declare types without names. If you are concerned about their limitations and don't want to use them then you can use Func<T, TResult> generic delegate in lieu of anonymous types.

Listing 1.4
C#
Func<int,DataTable> GetUserDetails = userID => { return new DataTable(); };
var dt = GetUserDetails(1);
VB.NET

Dim GetUserDetails As Func(Of Integer, DataTable) = Function(x) New DataTable
Dim dt = GetUserDetails(1)



HTH,

LINQ to SQL - Multiple result shapes

Working with LINQ to SQL, you might come across a situation where your stored procedure looks something like this:

CREATE PROCEDURE [dbo].[GetAllProductsAndCustomers]
@CompanyID INT
AS
SELECT [Code, Category] FROM Products
SELECT [Name, Email,Contact] FROM Customers

This SP returns multiple result sets which LINQ supports quite efficiently. However, If you work with auto-generated object relational mapper (*.dbml), you must have noticed that CLR cannot automatically determine which stored procedure returns multiple result shapes, hence creates a wrapper function with ISingleResult as a return type, which represents the result of a mapped function that has a single return sequence. For the above SP, it generated the below method signature:

<FunctionAttribute(Name:="GetAllProductsAndCustomers")> _
Public Function GetAllProductsAndCustomers(<Parameter(Name:="CompanyID", DbType:="Int")> ByVal CompanyID As System.Nullable(Of Integer)) As ISingleResult(Of GetAllProductsAndCustomersResult)
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me, CType(MethodInfo.GetCurrentMethod,MethodInfo), companyID)
Return CType(result.ReturnValue,ISingleResult(Of GetAllProductsAndCustomersResult))
End Function

In order to turn this situation into our favor and handle multiple result shapes returned by the stored proc, all we need to do is replace the ISingleResult with IMultipleResults and supply the appropriate result types. If in case there is no specific result type, which is quite possible if stored proc is generating columns from multiple tables as a result of join, you can provide any names and LINQ will treat them as anonymous types. In the method signature below, I have created two classes GetAllProductsAndCustomersResult1 and GetAllProductsAndCustomersResult2 along with the properties Code and Category in the first and Name, Email, Contact in the second class.

One important thing, for the kind of SP we are using we need to read the result shapes in the same sequence as the SP returns the results. The order of IMultipleResults.GetResult() should be same as the order of SELECT statements in SP in order to avoid getting unexpected results or errors and exceptions if our IEnumerable result set is bound to a data source control.

The modified method signature will look like:

<FunctionAttribute(Name:="GetAllProductsAndCustomers"), _
ResultType(GetType(GetAllProductsAndCustomersResult1)), _
ResultType(GetType(GetAllProductsAndCustomersResult2))> _
Public Function GetAllProductsAndCustomers(<Parameter(Name:="DataBridgeQueueID", DbType:="Int")> ByVal companyID As System.Nullable(Of Integer)) As IMultipleResults
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me, CType(MethodInfo.GetCurrentMethod,MethodInfo), companyID)
Return CType(result.ReturnValue,IMultipleResults)
End Function

HTH,