Linq to XML Related Posts

LINQ in Action XML samples added to LINQPad

The beginning of this month, we released the samples from "LINQ in Action" for chapters 1-8 which covers LINQ to Objects, LINQ to SQL, and the new Language features. We're happy to announce that the next three chapters covering LINQ to XML are now available. That's over 70 new samples in VB and C# each. Follow the directions on the original announcement to download and use these samples.

Posted on 6/28/2009 2:03:00 PM - Comments(2)
Categories: Linq to XML LinqPad

More Ways to create an XML file

Edward Tanguay just posted three comparative examples of creating XML files via String Concatenation, XmlWriter, and LINQ to XML. I would typically respond to a blog post through that site's comments, but public comments are blocked at this point. While his post is a good start and shows pretty easily the simplicity that using LINQ to XML gives over the other options, he does fail to address a couple other options:

  • XmlDocument
  • Serialized objects, and of course
  • XML Literals with VB.

Before discussing each of these options, I want to take a minute to address the real issues with the string concatenation sample. While it is relatively easy to use string concatenation to build up the XML, it can be quite dangerous. The main issue is the ease at which you can add invalid characters into the XML elements. In particular, if your variable has one of the characters that need to be escaped (like & or <), using the sample code will cause invalid XML to be generated. You have to properly escape all of the variables which can quickly make the code much more complex. Luckily each of the other XML options handle escaping these values correctly for you automatically.

On to the other options. While you could use the XmlDocument rather than XmlWriter, coding it is much more complex. Also, it doesn't allow for streaming the results out which causes a higher memory footprint as the entire document will be in memory.

Serialized objects give a nice type-safe option, but they do require you to code the object structures before you can consume them. Using tools like XSD.exe or LINQ to XSD helps reduce this overhead, but it is something to be aware of.

I would be remiss if I didn't offer a XML Literal option, so here you go. Here's Edward's C# Sample for reference purposes:

 
XDocument doc = new XDocument(
                new XDeclaration("1.0", null, null),
                new XElement(_pluralCamelNotation,
                    Enumerable.Range(1, 3).Select(
                        i => new XElement(_singularCamelNotation,
                            _allDataTypes.Select(
                                dataType => new XElement(
                                    dataType.CamelCaseNotation,
                                    dataType.GetDummyData())
                            )
                    ))));
            return doc.ToString();

And here's the VB equivalent using XML Literals:


Dim doc = <?xml version="1.0"?>
          <<%= _pluralCamelNotation %>>
              <%= From i In Enumerable.Range(1, 3) _
                  Select <<%= _singluarNotation %>>
                           <%= From t In _allDataTypes _
                               Select <<%= t.CamelCaseNotation %>>
                                           <%= t.GetDummyData %></> %>
                           </> %>
               </>
As it is, we are able to drop a couple lines from the C# code. However, this example is a bit less consise as some XML Literal examples because we are not only inserting the element's values, but the names of the elements themselves dynamically through the literal. As a result there are a number of cases where we have double opening XML tags (<<) which detracts from the readability somewhat. Choosing to use LINQ expressions over Lambda's is purely a matter of style and preference.

Posted on 6/24/2009 9:04:00 PM - Comments(0)
Categories: Linq to XML

Fetching XML from SQL Server using LINQ to SQL

With SQL Server, you can use the For Xml clause (read more in BOL). The quickest option is to add For XML Auto at the end of a SQL statement. You can do this with dynamic SQL or inside a stored proc. If you use a stored proc, the DBML tool doesn't recognize this as XML (and return it as an XElement as it does for XML Data type columns).

Regardless of whether you are using stored procs or dynamic SQL, the server returns the result as an array of strings broken up into 4000 character chunks. It is your responsibility to piece this back together. You can concatenate the strings and parse the XML, however there is no true root node in this return set, only a series of XML elements.

Since you are not going to be able to rely on the generated method stub for the procedure, you may want to consider using ExecuteQuery directly and handle the string parsing. If you define this in a partial class for your context, it will appear to calling code as if it came directly from the database pre formatted.  For example, here is some code that returns the customers from Northwind as an XElement:

Public Function CustomerAsXml() As XElement
    Dim returnVal = Me.ExecuteQuery(Of String)("Select * from Customers For XML Auto")
    Dim fullString = String.Concat((From x In returnVal Select x).ToArray)
    Dim xml = XElement.Parse("<root>" & fullString & "</root>")
    Return Xml
End Function

You could substitute the name of your stored proc with parameters in the place of this dynamic SQL and it should work equally well.

Dim returnVal = Me.ExecuteQuery(Of String)("CustomersXml", New Object() {})
 
Posted on 6/24/2009 3:39:00 PM - Comments(2)
Categories: LINQ VB VB Dev Center Linq to XML

Adding a dynamic SiteMap for search engine optimization using LINQ

A couple months ago I added a feature to this site to build a Site Map for this site dynamically based on the information from the database for posts and files for the downloads. If your not familiar with how Sitemap files can help your site searchability, Google has a good documentation about Sitemaps in their Webmaster tools.

The SiteMap Protocal is a rather simple XML document consisting of a set of url nodes that consist of the following:

  • loc - URL for the page link
  • lastmod - Date the page was last modified
  • changefreq - How often the page is changed
  • priority - How high you think the page should be ranked relative to other pages on your site.

For this site, I decided to index the main (default.aspx) File, about and contact pages. In addition, I indexed each post as a separate url node. If you want to view the resulting data, browse to http://www.thinqlinq.com/sitemap.aspx. To do this, I used LINQ to XML with VB XML Literals. To begin, we need to add the XML Namespaces. At the top of our file, we enter the following imports:

Imports <xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
Imports <xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

We continue by adding the root urlset node and one child node representing the main page:

Dim map = _

    <?xml version='1.0' encoding='UTF-8'?>

    <urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9

            http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"

        xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

 

        <url>

            <loc>http://www.ThinqLinq.com/default.aspx</loc>

            <lastmod>

               <%= (From p In dc.PostItems _

                    Order By p.PublicationDate Descending) _

            .First.PublicationDate.ToString("yyyy-MM-dd") %>

            </lastmod>

            <changefreq>daily</changefreq>

            <priority>1.0</priority>

        </url>

    </urlset>

 

Most of this is standard XML. The main difference is the use of a LINQ query to show the last modification date based on the most recent post from our database. In this case we just want the First date when the dates are ordered descending. We do need to format it properly so that our search engine (Google) will be able to recognize it.

Next up, we need to add the link for the Downloads page. We'll do this much the same way that we added the url for the default page. However, in this case the modification date won't come from the database, but rather use a LINQ to Objects query to get the most recent file in the downloads directory on this site.

<url>

    <loc>http://www.ThinqLinq.com/Files.aspx</loc>

    <lastmod>

        <%= (From f In New System.IO.DirectoryInfo( _

            Server.MapPath("~/Downloads")).GetFiles _

            Order By f.LastWriteTime Descending) _

            .FirstOrDefault.LastWriteTime.ToString("yyyy-MM-dd") %>

    </lastmod>

    <changefreq>weekly</changefreq>

    <priority>1.0</priority>

</url>

The About and Contact pages are relatively straight forward. The remaining url nodes are generated based on the records in the PostItems from our database. To populate them, we'll create a LINQ query pulling the data from the database using LINQ to SQL and projecting (Select) out individual url nodes for each row in the database:

<%= From p In dc.PostItems.ToList _

    Select <url>

               <loc>http://www.ThinqLinq.com/default/<%= p.TitleUrlRewrite %>.aspx</loc>

               <lastmod><%= p.PublicationDate.ToString("yyyy-MM-dd") %></lastmod>

               <changefreq>daily</changefreq>

               <priority>0.3</priority>

           </url> %>


As you can see, there isn't much here that is overly complex. It's just a series of LINQ queries filling the data from various sources. For reference purposes, Here's the complete code:

Imports <xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

Imports <xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

 

Partial Class SiteMap

    Inherits System.Web.UI.Page

 

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

        Response.Buffer = True

        Response.Clear()

        Response.Cache.SetCacheability(HttpCacheability.NoCache)

        Response.ContentType = "text/xml"

        Response.AddHeader("Content-Disposition", "inline;filename=blog.rss")

        WriteRss()

        Response.End()

    End Sub

 

    Private Sub WriteRss()

        Try

            Using dc As New LinqBlog.BO.LinqBlogDataContext

                Dim map = _

                    <?xml version='1.0' encoding='UTF-8'?>

                    <urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"

                        xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

 

                        <url>

                            <loc>http://www.ThinqLinq.com/default.aspx</loc>

                            <lastmod>

                                <%= (From p In dc.PostItems _

                                    Order By p.PublicationDate Descending) _

                                    .First.PublicationDate.ToString("yyyy-MM-dd") %>

                            </lastmod>

                            <changefreq>daily</changefreq>

                            <priority>1.0</priority>

                        </url>

                        <url>

                            <loc>http://www.ThinqLinq.com/Files.aspx</loc>

                            <lastmod>

                                <%= (From f In New System.IO.DirectoryInfo( _

                                    Server.MapPath("~/Downloads")).GetFiles _

                                    Order By f.LastWriteTime Descending) _

                                    .FirstOrDefault.LastWriteTime.ToString("yyyy-MM-dd") %>

                            </lastmod>

                            <changefreq>weekly</changefreq>

                            <priority>1.0</priority>

                        </url>

                        <url>

                            <loc>http://www.ThinqLinq.com/about.aspx</loc>

                            <lastmod>

                                <%= System.IO.File.GetLastWriteTime( _

                                    Server.MapPath("About.aspx")).ToString("yyyy-MM-dd") %>

                            </lastmod>

                            <changefreq>monthly</changefreq>

                            <priority>1.0</priority>

                        </url>

                        <%= From p In dc.PostItems.ToList _

                            Select <url>

                                       <loc>http://www.ThinqLinq.com/default/<%= p.TitleUrlRewrite %>.aspx</loc>

                                       <lastmod><%= p.PublicationDate.ToString("yyyy-MM-dd") %></lastmod>

                                       <changefreq>daily</changefreq>

                                       <priority>0.3</priority>

                                   </url> %>

 

                        <url>

                            <loc>http://www.ThinqLinq.com/Contact.aspx</loc>

                            <lastmod>2008-02-28</lastmod>

                            <changefreq>never</changefreq>

                            <priority>0.1</priority>

                        </url>

                    </urlset>

                Response.Write(map)

            End Using

        Catch ex As Exception

            Response.Write(<error><%= ex.ToString %></error>)

        End Try

 

    End Sub

End Class

Posted on 1/3/2009 3:32:00 PM - Comments(7)
Categories: VB Dev Center LINQ VB VS 2008 SEO Linq to XML

Screen scraping and creating Word documents with LINQ to XML

At TechEd Developers 2008 in Orlando, I had the pleasure of competing in Speaker Idol. In that competition, we had the opportunity to present a topic in 5 minutes. Unfortunately, the topic I choose really needed 10 minutes to cover at the level of detail it needed. Instead of limiting the topic, I decided to go ahead and present it a bit too fast.

If you want to see the video, or see how to use VB 9's XML Literals and LINQ to XML to fetch data from a web page (that must be XHtml compilant), manipulate it and insert it into a Word 2007 file, it is now available on the  Developer Landing page, and the Library page. of the TechEd site. If you prefer, you can jump right to the video in either WMV or MP4 file formats. If you're not familiar with LINQ to XML, go ahead and download the video and just watch it at half speed ;-)

Posted on 6/12/2008 1:13:00 PM - Comments(3)
Categories: VB Dev Center LINQ VB Linq to XML

Querying the complete plays of Shakespeare using LINQ to XML

I was working to come up with some creative uses of LINQ to XML for my new talk I'm giving at the Huntsville, AL Code Camp. I figured it would be good to include a sample which queries a large XML document. Remembering that the complete works of Shakespeare were available in XML form, I did a quick search and found a version at http://metalab.unc.edu/bosak/xml/eg/shaks200.zip. This file separates each play out into separate XML files. Since I wanted to find out which parts had the most lines across all plays, I wanted to put them into a single XML file. Rather than doing this manually, I went ahead and whipped up a quick LINQ query to fetch the xml documents and load them up into an array of XElements:

Dim plays = _
    From file In New System.IO.DirectoryInfo("C:\projects\ShakespeareXml").GetFiles() _
   
Where file.Extension.Equals(".xml", StringComparison.CurrentCultureIgnoreCase) _
   
Let doc = XElement.Load(file.FullName) _
   
Select doc

Ok, now that out of the way, I really wanted to load up a single XML file with these resulting nodes. Pretty easy using XML Literals. Just wrap the query with a new root element:

Dim plays = _
  
<Plays>
   
<%= From file In New System.IO.DirectoryInfo("C:\projects\ShakespeareXml").GetFiles() _
   
Where file.Extension.Equals(".xml", String Comparison.CurrentCultureIgnoreCase) _
   
Let doc = XElement.Load(file.FullName) _
   
Select doc %>
  
</Plays>

Easy. Now I have a new XML document containing the complete plays of Shakespeare. Now, what can we do with it... Well, we can get a count of the plays in one line:

Console.WriteLine("Plays found: " & plays.<PLAY>.Count.ToString)

We could have done that without putting it into a new document. We do see that we have 37 plays represented, so we know the first query worked. Now, to count the number of lines (LINE) for each character (SPEAKER). The XML document groups each set of lines into a parent node called SPEECH. This SPEECH node then contains the SPEAKER element and a series of LINE elements. For example, here's the beginning of Juliet's fameous Romeo, Romeo speech:

<SPEECH>
   
<SPEAKER>JULIET</SPEAKER>
   
<LINE>O Romeo, Romeo! wherefore art thou Romeo?</LINE>
   
<LINE>Deny thy father and refuse thy name;</LINE>
   
<LINE>Or, if thou wilt not, be but sworn my love,</LINE>
   
<LINE>And I'll no longer be a Capulet.</LINE>
</SPEECH>

So to achieve the goal of counting our lines by character, we find the descendent nodes of the plays element (plays...<SPEECH>) and group them by the speaker. Then we project out the name of the speaker and the number of lines they have. We don't care about the bit roles, so we'll order the results in descending form based on the number of lines (LineCount). We'll limit the results to the top 50 entries. Here's the resulting query:

Dim mostLines = _
  
From speech In plays...<SPEECH> _
  
Group By key = speech.<SPEAKER>.Value Into Group _
  
Select Speaker = key, _
            LineCount =
Group.<LINE>.Count _
  
Order By LineCount Descending _
  
Take 50

The amazing thing with this process, running all three queries here, including the one which loads the full XML from the various files takes less than a second. I haven't had time to do a full performance test, including memory load, but the initial results are quite impressive!

If you have other creative uses of LINQ to XML, let me know, I'd love to include them in future presentations. Also, if you're in the Huntsville, AL area on 2/23/2008, head on over to the code camp and see the entire presentation in person.

Posted on 2/22/2008 5:46:00 PM - Comments(3)
Categories: VB Dev Center LINQ VB Linq to XML

Adding categories to the RSS feed using LINQ to XML

Last time, we added categories to the web view of the ThinqLinq site. This time, we're going to add them to the RSS feed. Because RSS is "Really Simple", adding the categories is fairly easy. According to the RSS specification, <category> is an optional sub-element of <item>. It can additionally contain the domain that contains that category. In our case, we will point the domain to our implementation that displays all posts for a given category by passing the category id to the query string of our default page. Thus we would want to insert a category like follows:

<category domain="http://www.ThinqLinq.com/default.aspx?CategoryId=1">LINQ</category>

To refresh your memory, we created last discussed creating RSS in the following post: http://www.thinqlinq.com/Default/Projecting_XML_from_LINQ_to_SQL.aspx. To add our comments, we need to inject an additional query before inside of each <item> element for the categories. To generate each of the categories for a post, we add a query to project (using the select statement) our new <category> nodes as follows:

     <%= From c In post.CategoryPosts _
              Select <category domain=<%= "Default.aspx?CategoryId" & c.CategoryID %>>
             
<%= c.Category.Title %></category> %>

Notice here, when we are creating the domain, we can use the CategoryID of the CategoryPosts object from the many-to-many table. To display the actual post's title, we need to drill through the CategoryPost to the child Category object's Title property (c.Category.Title). Putting this all together, Using the magic of LINQ to XML and VB, we arrive at the following statement:

Response.Write(<?xml version="1.0" encoding="UTF-8"?>
 
<rss version='2.0' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:slash='http://purl.org/rss/1.0/modules/slash/' xmlns:wfw='http://wellformedweb.org/CommentAPI/'>
 
<channel>
   
<title>Thinq Linq</title>
   
<link>http://www.ThinqLinq.com/default.aspx</link>
   
<description>Thoughts relating to LINQ and Language Integrated Query related topics.</description>
   
<dc:language>en-US</dc:language>
   
<generator>LINQ</generator>
   
<%= From post In query.ToArray _
            Select <item>
                        
<title><%= post.Title %></title>
                         <link><%= "http://www.thinqlinq.com/Default.aspx?Postid=" & post.Id.ToString %></link>
                         
<pubDate><%= post.PublicationDate.ToString("ddd, dd MMM yyyy hh:mm:ss GMT") %></pubDate>
                        
<guid isPermaLink="false">42f563c8-34ea-4d01-bfe1-2047c2222a74:<%= post.Id %></guid>
                        
<description><%= post.Description %></description>
                        
<%= From c In post.CategoryPosts _
                                  Select <category domain=<%= "Default.aspx?CategoryId" & c.CategoryID %>>
                                 
<%= c.Category.Title %></category> %>
                        
</item> %>
   
</channel>
  </rss>)

If you want to view the resulting feed, browse directly to http://www.thinqlinq.com/rss.aspx rather than using the standard RSS feed link which is managed through FeedBurner to allow me to gather additional statistics for this site.

Posted on 1/20/2008 9:09:00 PM - Comments(2)
Categories: VB Dev Center LINQ VB Linq to XML

Adding a RSS feed for file downloads

Ok, so I've been a bit busy this weekend adding some nice stuff for this site. One thing that I wanted to add was another RSS feed, this time for the file upload section. If you want to subscribe to the File RSS feed, direct your aggregator to the following link: http://www.thinqlinq.com/Files.aspx/Rss

Of course, since this is a learning site, I'll let you in on the code needed to accomplish the task. As you may guess, LINQ makes serving up XML from an object collection using a heterogeneous join to a database fairly easy. If you haven't seen how we display the files for this site, check out the podcast I did for Wally last year where we go over it in more detail.

To begin the task, we will set-up our initial query We will use two data sources for this task. The first data source is the object collection containing the FilInfo objects from the download directory for this site. (System.IO.DirectoryInfo(...).GetFiles). The second source is the FileClassic table returned using LINQ to SQL. LINQ allows us to easily join these two data sources and work with it as we would any other data source.

Dim fileDescriptions = dc.GetTable(Of FileClassic)()
Dim files = (From f In New System.IO.DirectoryInfo(Server.MapPath("~/Downloads")).GetFiles _
                  Join desc In fileDescriptions On f.Name Equals desc.FileName _
                  Order By f.LastWriteTime Descending _
                  Select URL = "Downloads/" & f.Name, _
                  Name = System.IO.Path.GetFileNameWithoutExtension(f.Name), _
                  f.LastWriteTime, f.Length, _
                  Description = Server.HtmlDecode(desc.Description), desc.Id).ToArray

With our query prepared, we can now use LINQ to XML with VB Literals to generate the RSS feed and output it to the response stream as follows:

Response.Write(<?xml version="1.0" encoding="UTF-8"?>
 
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/">
    <channel>
    <title>ThinqLinq samples and presentations</title>
    <link>http://www.ThinqLinq.com/Files.aspx</link>
    <description>Here are some of my presentation files available for you to use as you wish.  Just let me know if you like what you see and what you want to see more of.</description>
   
<%= From f In files _
            Select <item>
                       
<title><%= f.Name %></title>
                       
<link><%= "http://www.ThinqLinq.com/" & f.URL %></link>
                       
<pubDate><%= f.LastWriteTime.ToString("ddd, dd MMM yyyy hh:mm:ss GMT") %></pubDate>
                       
<guid isPermaLink="false"><%= "42f563c8-34ea-4d01-bfe1-2047c2222a74:" & f.Id %></guid>
                       
<description><%= f.Description %></description>
                      
<enclosure url=<%= "http://www.ThinqLinq.com/" & f.URL %>
                                        
length=<%= f.Length %>
                                         type="application/x-zip-compressed"
/>
                     
</item> %>
   
</channel>
 
</rss>)

The main difference between this example and the ones we are using for blog posts is the addition of the <enclosure> node. With enclosure, we specify the url to the file download, the file length, and the file type. In this site, I plan to always use ZIP files so I just hard code that. The other values come from our starting query. If you subscribe to this new feed, you should see each item including an enclosure icon so that you can download it.

Posted on 1/20/2008 8:38:00 PM - Comments(2)
Categories: VB Dev Center LINQ VB Linq to XML

Projecting XML from LINQ to SQL

Among the new cool features in Visual Studio 2008, one of the best may be the XML Literal support with VB 9 and LINQ. In my last post, I mentioned some changing features from the Beta to RTM. One that could easily be overlooked is a change to the way LINQ to SQL can now directly project into XML literals.

Through the Beta cycle, there was an issue with projecting XML elements directly from a LINQ to SQL query. If you haven't seen LINQ to SQL with XML, here's a code sample that explains what I'm referring to:

Dim dc As New LinqBlogDataContext
'Formulate the Query to get the last 10 blog posts
Dim query = (From p In dc.PostItems _
                     
Order By p.PublicationDate Descending _
                     
Take 10 _
                      
Select p).ToArray

'Create a root Site node with 10 child "item" nodes.
'Each node will be filled in through a LINQ query
Dim fooShort = _
 
<site>
       
<%= From p In query _
                
Select _
            
<item>
                 
<title><%= p.Title %></title>
                 
<link>http://ThinqLinq.com/Default.aspx?Postid=<%= p.Id %></link>
                 
<pubDate><%= p.PublicationDate %></pubDate>
                 
<guid isPermaLink="false">42f563c8-34ea-4d01-bfe1-2047c2222a74:<%
p.Id %></guid>
                 
<description><%= p.Description %></description>
                
</item> %>
 
</site>

In this code, I'm performing two queries. The first one sets up the LINQ to SQL query and pre-fetches the results into an Array. In the beta builds, if we didn't include the pre-fetching ToArray, the second query which projects the results of the first into individual <item> nodes. What is the difference between these queries? The first query uses LINQ to SQL and projects results directly from the database. Because we pre-fetch the results into an array of objects, the resulting query only uses LINQ to Objects rather than the direct LINQ to SQL implementation.

With the final RTM of Visual Studio, we no longer need to pre-fetch the results from the query. Instead, we can directly project our desired XML from the select statement without needing the intermediary step. Here is the revised code. Notice, we can now perform the same result with a single LINQ query rather than two.

Dim fooNew = _
 
<site>
      
<%= From p In dc.PostItems _
                Order By p.PublicationDate Descending _
               
Take 10 _
                
Select _
           
<item>
               
<title><%= p.Title %></title>
               
<link>http://ThinqLinq.com/Default.aspx?Postid=<%= p.Id %></link>
               
<pubDate><%= p.PublicationDate %></pubDate>
               
<guid isPermaLink="false">42f563c8-34ea-4d01-bfe1-2047c2222a74:<%= p.Id %></guid>
               
<description><%= p.Description %></description>
           
</item> %>
   
</site>

The result is more concise. You may find you want to continue separating your query definition from your XML creation in order to improve maintainability. If this is the case, simply keep the first code sample and remove the call to .ToArray. Because LINQ to SQL is composable, you can separate the queries into two code sets. When the query is evaluated, the two expressions will be combined into a single query to the database and the projection will continue to work.

Enjoy working with VB 9 and XML. In my opinion it is one of the killer features of Visual Studio 2008. If you give it a try, I think you might find the same.

Posted on 11/20/2007 12:00:00 AM - Comments(0)
Categories: VB LINQ Linq to XML

Creating HTML emails using VB 9 and LINQ

Today, I'm not looking at sending mass spam using LINQ to pull a list of recipients. I'm actually referring to the ability to generate the message body using XML Literals. Using the System.Net.Mail.MailMessage object, we can easily send emails to an SMTP server.

The body of the email can either be plain text or HTML. Dynamically generating the text is often a laborious task involving a string builder and lots of method calls. The body corresponds to the body portion of a HTML page. If you use well formed XHTML in the body, you are actually generating a specialized version of XML. Once we are working with XML, we can use XML Literals in VB to format our output.

I recently had to do this on a project to send lists of updated values from an external source. In the body, I needed to dynamically fill a HTML table with the new values. The table consists of 4 columns: State, County, Limit, Effective Date. I begin by laying out the content in a HTML editor (like Visual Studio 2008...) Here's the results:

<body>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur eros purus, suscipit ac, pulvinar vel, aliquet vehicula, pede. Duis eros dolor, iaculis non, aliquam sed, tincidunt ac, diam.
   
<table>
     
<tr><th>State</th>
           
<th>County</th>
           
<th>New Limit</th>
           
<th>Effective Date</th>
     
</tr><tr><td>XX</td>
                     
<td>Foo</td>
                    
<td>$123</td>
                     
<td>1/1/2000</td>
               
</tr>
   
</table>
 
</body>

I know what you must be thinking by now: Gee Jim, how could you come up with such a beautiful page. As Bones would say, "D@mmit Jim, I'm programmer not a designer." We'll keep it clean for now to focus on what is yet to come.

Realize that our body tag is actually the root of a well-formed XML document. As such, we can copy it as a template directly into our tool (which is a console application by the way), add a reference to System.Linq and System.Xml.Linq, and paste it into our VB module assigning a variable, let's call it "body" to the XML.

While we're at it, we'll go ahead and insert new rows into the table based on the results of an object query. In this query, we'll iterate over the records we are adding which is an IEnumerable(Of Limit). We'll project a new row (<tr>) for each object in our iteration. Rather than imperatively iterating, we'll use LINQ's declarative syntax In addition, we'll insert our values using the <%= %> place holders. Here's the resulting declaration:

Friend Shared Sub SendUpdate(ByVal newItems As IEnumerable(Of FhaLimit))
Dim body = _
  <body>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur eros purus, suscipit ac, pulvinar vel, aliquet vehicula, pede. Duis eros dolor, iaculis non, aliquam sed, tincidunt ac, diam. 
    <table>
     
<tr><th>State</th>
           
<th>County</th>
           
<th>New Limit</th>
           
<th>Effective Date</th>
     
</tr><%= From limit In newItems _
                      Order By limit.State, limit.CountyName _
                      Select <tr><td><%= limit.State %></td>
                                      
<td><%= limit.CountyName %></td>
                                      
<td><%= limit.Units1.Value.ToString("c0") %></td>
                                      
<td><%= limit.LimitTransactionDate.ToShortDateString %></td>
                               
</tr> %>
   
</table>
 
</body>

If you've coded ASP.NET, the resulting declaration should look very familiar. Realize that this is being done in a VB module in a console application. We are not coding in a .ASPX file! The resulting maintainability of this code is much better than the old way using string builders or XSLT.

To finish off the task, we are going to send the message with our new XHTML body. This is very easy with .NET as well.

Dim message As New System.Net.Mail.MailMessage("from@ThinqLinq.com", "to@ThinqLinq.com", "Limits Updated", body.ToString)
message.IsBodyHtml = True
Dim server As New System.Net.Mail.SmtpClient(My.Settings.SmtpServer)
server.Send(message)

There you go, a quick and painless way to create HTML emails using VB 9 and LINQ. Let me know what you Thinq.

Posted on 10/24/2007 12:00:00 AM - Comments(0)
Categories: LINQ VB VB Dev Center Linq to XML

Using LINQ to query against objects and XML

In preparing for my LINQ talk for DBA's at tomorrow's Atlanta Microsoft Database Forum user group meeting, I thought it would be good to offer a quick look at the LINQ to Objects and LINQ to XML stacks before focusing on the OR mapping portions: LINQ to SQL,  LINQ to Entities and LINQ to dataSet. I previously used a quick demo which joined  the FileInfo collection returned by DirectoryInfo.GetFiles with an object collection built from a CSV file. For this demo, I wanted to add in the XML stack. Making this change is a relatively minor task.

First, I converted the CSV file to a simple XML document. See my previous post to see how straight-forward the conversion is. Once converted, our XML looks like the following:

<FileTypes>
   <FileType extension=".doc" description="Microsoft Word Document" />
   <FileType extension=".log" description="Log File" />
   <FileType extension=".msg" description="Mail Message" />
   <FileType extension=".rtf" description="Rich Text Format" />
   <FileType extension=".txt" description="Text File" />
   <FileType extension=".wpd" description="WordPerfect Document" />
</FileTypes>

To query this, we need to first load the XML document. LINQ to XML offers a single line method to load a XML document using the simple "Load" method.

Dim FileTypes As XDocument = XDocument.Load("C:\FileTypes.xml")

Once we have the document, we can easily get a collection of elements using the .Descendents("FileType") method. VB 9 offers a more concise method which retains a more type safe mechanism. Here we identify the specific nodes we want to access (FileTypes.<FileTypes>.<FileType>). With this collection in hand, we can replace the old CSV based collection with the new XML collection using the following LINQ query.

Dim results = _
From fi In New DirectoryInfo("C:\Windows").GetFiles, _
ext In FileTypes.<FileTypes>.<FileType> _
Where fi.LastAccessTime >= Now.AddDays(-6) _
And fi.Extension = ext.Attribute("extension").Value _
Select New {FileName := fi.Name, lastAccess := fi.LastAccessTime, Exten := ext.Attribute("description").Value} _
Order By FileName Descending

Posted on 1/8/2007 3:10:00 AM - Comments(0)
Categories: LINQ Linq to XML

Use LINQ to convert CSV to XML

File conversion is a typical programming task. VB9 should make this task easier to code and maintain as we can eliminate much of the plumbing code and focus on the task at hand. The following example uses the MAY CTP of LINQ and leverages the TextFieldParser added to VB 8 to convert a simple CSV file to XML. For this sample, we will convert a simple CSV file that contains standard windows file extensions with a brief description of the file. Here are the first couple of lines:

.doc,Microsoft Word Document
.log,Log File
.msg,Mail Message
.rtf,Rich Text Format
.txt,Text File

For this task, I want to create an XML document with a single root node and multiple child nodes with the extension and description as attributes of a FileType element as follows:

<FileTypes>
   <FileType extension=".doc" description="Microsoft Word Document" />
   <FileType extension=".log" description="Log File" />
   <FileType extension=".msg" description="Mail Message" />
   <FileType extension=".rtf" description="Rich Text Format" />
   <FileType extension=".txt" description="Text File" />
   <FileType extension=".wpd" description="WordPerfect Document" />
</FileTypes>

To accomplish this, we need to do 3 simple steps: iterate through the csv file, Add the data to our XML document, save the document to disk. First, here is the complete code. We will examine each part below.

Public Shared Sub CsvXml(ByVal sourceFile As String, ByVal destinationFile As String)

   'Create a base XML element called FileTypes
   Dim value As New XElement("FileTypes")

   'Create a new delimited file reader using VB 2005's TextFieldParser
   Using reader As New Microsoft.VisualBasic.FileIO.TextFieldParser(sourceFile)
      reader.Delimiters = New String() {","}

      'Read data from the CSV
      While Not reader.EndOfData
         Dim currentRow() As String = reader.ReadFields

         'Use XLINQ to add the child elements to the base element
         value.Add(<FileType extension=<%= currentRow(0) %> description=<%= currentRow(1) %>/>)
      End While
   End Using

   'Save it
   value.Save(destinationFile)
End Sub

With LINQ to XML changes the model for XML from a document centric model to an Element centric model, thus to begin working with XML, we simply need to create a new element

   Dim value As New XElement("FileTypes")

Next, we need to open and read the lines of the CSV file. VB 2005 added a helpful class called the TextFieldParse which takes a stream, uri, or TextReader. To be simple, we will just use a string for the uri of the file we want to open. We also need to identify the kind of delimiter the CSV file uses. In this case, we are using a comma delimited file, so we will add a "," as the array of delimiters. In addition, we will leverage the new "Using" keyword to make sure we dispose of our file once we are done with it, even if we error while iterating through the file.

   Using reader As New Microsoft.VisualBasic.FileIO.TextFieldParser(sourceFile)
      reader.Delimiters = New String() {","}

To read each line, we set up a loop to continue iterating until we are out of data. We get the actual values for each line by calling the ReadFields method of the TextFieldParser.

      'Read data from the CSV
      While Not reader.EndOfData
         Dim currentRow() As String = reader.ReadFields

Now that we have the data we want to work with we can leverage the new functional construction available in LINQ to XML to add new elements. Our element name is FileType and has two attributes--extension and description. Using the <%=  ... %> syntax familiar to ASP or CodeSmith users we can easily insert the values from our CurrentRow array of values into the appropriate locations

         'Use XLINQ to add the child elements to the base element
         value.Add(<FileType extension=<%= currentRow(0) %> description=<%= currentRow(1) %>/>)
      End While
   End Using

Perhaps the easiest part of the process is to save the resulting XML element. LINQ to XML includes a simple Save method on the element which saves the element and all of it's children with a simple call.

   value.Save(destinationFile)
End Sub

Naturally, this sample could be extended easily to be more flexible. In particular, taking a CSV file with the names of the fields in the first row and dynamically building an XML document based on this CSV would be a helpful extension which I may try to demonstrate when I have more time.

Posted on 1/8/2007 2:40:00 AM - Comments(1)
Categories: LINQ Linq to XML