Willy’s Cave Dwelling Notes #6 – LINQ (Language Integrated Query)

Continued from Notes #3 – C# … re-visiting some interesting features, a quick de-tour through Notes #4 – Robert MacLean and co. poster and other gems and a switch to Notes #5 – C# and Asynchronous Programming. The more I dust-off my memory and the more I dig further into .NET/C# features, the more excited I get … what used to take a lot of time and effort to program has become intuitive and simplified.

Code used in this post can be downloaded from: Supporting Guidance and Whitepapers. This post uses the 5_Linq project in the CSharp_Feature_Tour solution.
IMPORTANT: The sample code only focuses on key aspects, is not intended for production and intentionally does not implement error and exception handling code.


Overview

For detailed information please refer to LINQ (Language-Integrated Query). This post scratches the top of the iceberg!

What I find amazing with LINQ and the associated C# enhancements it is creating a consistent and compact coding style for data access, whether we collaborate with Objects, XML, Entities or SQL in terms of data. As I have always had a problem with the complexity and amount of code one had to write to interact with XML, I decided to use LINQ to XML for my Cave Dwelling example.

The basic LINQ query can be written in two common styles known the comprehension and lambda query styles, as shown below:

// Comprehension Query Style
var almRangers = from individual in community // Sequence … what source are we querying
where individual.quality == passion // Query operator
&& individual.quality == experience // Query operator
select individual; // Query operator

// Lambda Query Style
var almRangers        = community.GetCollection()
.Where( (i) => i.quantity == passion & i.quantity == experience)
.Select ( (i) => i );

In essence we structure the query by saying: (1) from where we want to get the data, (2) where we define the range and filters and (3) what we will select.

Let the fun begin, using the following XML data file:

    1: <?xml version="1.0" encoding="utf-8" ?>
    2: <Solutions>
    3:   <Solution Key="1"  Name="Branching Guidance" URL="https://vsarbranchingguide.codeplex.com/" Type="guidance" />
    4:   <Solution Key="2"  Name="BRDLite Reference Templates" URL="https://vsarbuildguide.codeplex.com/" Type="tooling" />
    5:   <Solution Key="3"  Name="Build Customization Guidance" URL="https://vsarbuildguide.codeplex.com/" Type="guidance" />
    6:   <Solution Key="4"  Name="Coded UI Test Automation Guidance" URL="https://vsarcodeduiguide.codeplex.com/" Type="guidance" />
    7:   <Solution Key="5"  Name="Microsoft Test Manager Guidance" URL="https://vsartestmanagerguide.codeplex.com" Type="guidance" />
    8:   <Solution Key="6"  Name="Practical Kanban Guidance" URL="https://vsarkanbanguide.codeplex.com/" Type="both" />
    9:   <Solution Key="7"  Name="Practical Ruck Guidance" URL="https://vsarguidance.codeplex.com/" Type="guidance" />
   10:   <Solution Key="8"  Name="Rangers Personas and Scenarios" URL="https://vsarguidance.codeplex.com/" Type="guidance" />
   11:   <Solution Key="9"  Name="Requirements Engineering Guidance" URL="https://vsartfsreguide.codeplex.com/" Type="guidance" />
   12:   <Solution Key="10" Name="Team Foundation Server Azure Practical Guidance" URL="ttp://vsarguidance.codeplex.com/" Type="guidance" />
   13:   <Solution Key="11" Name="Team Foundation Server Process Template Customization Guidance" URL="https://vsartfsptguide.codeplex.com/" Type="guidance" />
   14:   <Solution Key="12" Name="Team Foundation Server Team Project Planning Guidance" URL="https://vsarplanningguide.codeplex.com/" Type="guidance" />
   15:   <Solution Key="13" Name="Team Foundation Server Upgrade Guidance" URL="https://vsarupgradeguide.codeplex.com/" Type="guidance" />
   16:   <Solution Key="14" Name="Test Release Management Guidance" URL="https://vsartestreleaseguide.codeplex.com/" Type="guidance" />
   17:   <Solution Key="15" Name="Visual Studio 2010 Coded UI Microsoft Word 2010 Add-in" URL="https://vsarcodeduiword.codeplex.com/" Type="tooling" />
   18:   <Solution Key="16" Name="Visual Studio Architecture Tooling Guidance" URL="https://vsararchitectguide.codeplex.com/" Type="guidance" />
   19:   <Solution Key="17" Name="Visual Studio Lab Management Guidance" URL="https://vsarlabman.codeplex.com/" Type="guidance" />
   20:   <Solution Key="18" Name="Visual Studio Quick Reference Guidance" URL="https://vsarquickguide.codeplex.com/project/edit" Type="guidance" />
   21:   <Solution Key="19" Name="VM Factory Guidance" URL="https://vsarvmfactory.codeplex.com/" Type="both" />
   22: </Solutions>


Reading the XML Data File

In the past I would have spend some time contemplating the code I would have to write and massaging my brain to remember all the APIs to call. Using LINQ makes it really easy:

    1: static XDocument LoadXmlFileAndData()
    2: {
    3:     var doc = new XDocument(XDocument.Load("ALMRangerSolutions.xml"));
    4:     return doc;
    5: }

If you want to build the XML Data yourself you would also not have to invest in any coffee breaks, just type up this code. Note that I only show adding two elements … reading the XML file is far easier and therefore my chosen path for this walkthrough:

    1: #region XML_INITIALIZATION
    2: static XDocument LoadXmlData()
    3: {
    4:     // create a lot of anonymous types with ... compact and easy to read syntax :)
    5:     var doc = new XDocument( new XComment("This is a comment"),
    6:                              new XElement( "Solutions",
    7:                                             new XElement("Solution",
    8:                                                           new XAttribute("Key", "1"),
    9:                                                           new XAttribute("Name", "Branching and Merging Guidance"),
   10:                                                           new XAttribute("URL","https://vsarbranchingguide.codeplex.com/"),
   11:                                                           new XAttribute("Type","Guidance")
   12:                                                         ), 
   13:                                             new XElement("Solution",
   14:                                                           new XAttribute("Key", "2"),
   15:                                                           new XAttribute("Name", "BRDLite Reference Templates"),
   16:                                                           new XAttribute("URL","https://vsarbuildguide.codeplex.com/"),
   17:                                                           new XAttribute("Type","Tooling")
   18:                                                         )
   19:                                         )
   20:                             );
   21:     return doc;
   22: }

Querying the XML Data

We will query the same data in three different ways, whereby there are many, many, many more ways. For the other ways, however, you are better off looking at the MSDN library documentation.

Option 1 – Comprehension Query Style

We can see the from-where-select query style, with an additional orderby operator. We filter on solution type and select the names.

    1: static void QueryData(XDocument doc, string typeSolution)
    2: {
    3:     // Comprehension Query Style
    4:     var  names = from s in doc.Element("Solutions").Elements("Solution")
    5:                  //from s in doc.Descendants("Solution")
    6:                  where s.Attribute("Type").Value == typeSolution
    7:                  orderby (string)s.Attribute("Name") ascending
    8:                  select s.Attribute("Name");
    9:  
   10:     DisplayElements(names, typeSolution);
   11: }

The resultant output delivers no surprises …

image

… but the Reflector shows us that the compiler has changed the code behind the scenes.

image

Option 2 – Lambda Query Style

We can rewrite the comprehension query style in the Lambda Query style, which looks very similar to the compiler generated code we just saw in reflector.

    1: static void QueryDataALaLambda(XDocument doc, string typeSolution)
    2: {
    3:     // Lamda Query Style
    4:     var names = doc.Element("Solutions").Elements("Solution")
    5:                    .Where((s) => s.Attribute("Type").Value == typeSolution)
    6:                    .OrderBy((s) => (string)s.Attribute("Name"))
    7:                    .Select((s) => s.Attribute("Name"));
    8:  
    9:     DisplayElements(names, typeSolution);
   10: }

Output is the same and even the Reflector code is identical:

image

Option 3 – “Greedy” Comprehension Style

If you go back to Option 1 and 2, set a breakpoint on line 4 (var names = …), run program and click on F11 (step-into) when you hit the breakpoint, you will notice that LINQ does not process the query until we reference the names in the DisplayElements(…) call. This is known as deferred execution.

image

To force execution, we can add a greedy operator ToList() as shown below.

    1: static void QueryDataGreedy(XDocument doc, string typeSolution)
    2: {
    3:     // Comprehension Query Style
    4:     var names = (from s in doc.Element("Solutions").Elements("Solution")
    5:                  where s.Attribute("Type").Value == typeSolution
    6:                  orderby (string)s.Attribute("Name") ascending
    7:                  select s.Attribute("Name")).ToList();
    8:  
    9:     DisplayElements(names, typeSolution);
   10: }

Redo the experiment and step into the statement and notice the difference … immediate execution. One of the side effects of the deferred execution is that in Option 1 and 2 we could add elements to the XDocument (doc) on line 91 below, which would be included in the select. In this option 3, the immediate execution would not pickup changes made after the from-where-select call.

image

As we build the sample solution we also notice that a lot of compiler generated code is added to our assembly … but we will lift that hood some other day.

image


More Operator Excursions

Instead I would much rather look at three more of the LINQ features which may come in handy.

Let Keyword

The Let keyword allows us to create a reusable alias range variable with which we can simplify huge and complex queries. In our simple sample solution it seems to add more noise than value, but if we imagine a normal business type query, we can quickly visualize the value.

    1: static void QueryDataLet(XDocument doc, string typeSolution)
    2: {
    3:     // Comprehension Query Style
    4:     var names = from s in doc.Element("Solutions").Elements("Solution")
    5:                 //Make query more readanle using let
    6:                 let attribute = s.Attribute("Name")
    7:                 where s.Attribute("Type").Value == typeSolution
    8:                 orderby (string)attribute ascending
    9:                 select attribute;
   10:  
   11:     DisplayElements(names, typeSolution);
   12: }

Into Operator

We can continue a query after a LINQ projection using into and creating a sub query. In our example we first look for (project) all the elements that match of solution type. We then continue the query by looking for solutions that contain Visual in the name within the projection.

We could extend the where clause to achieve the same result in this case.

    1: static void QueryDataInto(XDocument doc, string typeSolution)
    2: {
    3:     // Comprehension Query Style
    4:     var names = from s in doc.Element("Solutions").Elements("Solution")
    5:                 //from s in doc.Descendants("Solution")
    6:                 where s.Attribute("Type").Value == typeSolution
    7:                 orderby (string)s.Attribute("Name") ascending
    8:                 select s.Attribute("Name")
    9:                     into xSolutions
   10:                     where true == xSolutions.ToString().Contains("Visual")
   11:                     select xSolutions;
   12:  
   13:     DisplayElements(names, typeSolution);
   14: }

Join Operator

Lastly we will join two XML data files using the key attribute within the XML data and the join on operator.

    1: static void QueryDataJoin(XDocument docA, XDocument docB, string typeSolution)
    2: {
    3:     // Comprehension Query Style
    4:     var names = from s in docA.Element("Solutions").Elements("Solution")
    5:                 join t in docB.Element("Solutions").Elements("Solution")
    6:                   on s.Attribute("Key").Value equals t.Attribute("Key").Value
    7:                 select new
    8:                 {
    9:                     Project = s.Attribute("Name"),
   10:                     RelatedTo = t.Attribute("Name")
   11:                 };
   12:  
   13:     Console.ForegroundColor = ConsoleColor.Red;
   14:     var heading = "Solution Releationships";
   15:     Console.WriteLine(heading);
   16:     for (int i = 0; i < heading.Length; i++) Console.Write("-");
   17:     Console.ForegroundColor = ConsoleColor.Green;
   18:     Console.WriteLine();
   19:  
   20:     foreach (var project in names)
   21:     {
   22:         Console.WriteLine("Project: {0}, Related To: {1}",
   23:                           project.Project.Value,
   24:                           project.RelatedTo.Value);
   25:     }
   26: }

As shown in the output we have joined the two Elements from the two separate XML data files:

image


The community poster SDLC with VSTS - 0202 Microsoft Team System Orcas Top Seven Wonders is a bit dated, but still summarizes some of what we discussed quite nicely:

image

What’s next? Well I will focus on PowerShell in my next exploration and then switch to Windows 8 solution development. See you next time.