Using LINQ with ASP.NET (Part 1)

One of the new things I’m super excited about right now is the LINQ family of technologies that are starting to come out (LINQ, DLINQ, XLINQ and others soon).

 

LINQ will be fully integrated with the next release of Visual Studio (code-name: Orcas) and it will include some very cool framework and tool support (including full intellisense and designer support).  Last week the LINQ team released the May CTP drop of LINQ that you can download from here.  What is cool about this CTP is that it works with VS 2005, and allows you to start learning more about it immediately.  It incorporates a bunch of customer feedback (for example: support for stored procedures in DLINQ), and also includes a built-in ASP.NET Web-Site Project to enable you to leverage it with ASP.NET apps (note: you can also use LINQ with the new VS 2005 Web Application Project option as well).

 

I’m going to put together a few blog postings over the next few weeks that show off ways to use LINQ/DLINQ/XLINQ within ASP.NET projects.  This first walkthrough below will help you get started and introduce some of the important LINQ concepts.  You can follow-along by downloading the May CTP LINQ preview above and typing in the code below (I list all of it below), or you can download and run the complete .zip file of my samples here (note: you still need to install the LINQ May CTP drop for the .zip file of samples to work). 

 

Note: LINQ, DLINQ and XLINQ will be fully supported in both C# and VB.  I am using C# for the example belows.

 

Step 0: Creating a C# LINQ ASP.NET Web Site

 

To create a new ASP.NET Web Site that can use LINQ/DLINQ/XLINQ and the new C# 3.0 language features, choose File->New Web Site in VS and select the “LINQ ASP.NET Web Site Template”:

 

 

This will create a web-site project with the following files in-it by default:

 

 

Note that it includes a number of LINQ assemblies in the \bin folder.  It also adds the following setting to the app’s web.config file which tells both VS and ASP.NET to use the C# 3.0 compiler to compile and run the app:

 

    <system.codedom>

      <compilers>

        <compiler language="c#;cs;csharp"       

                  extension=".cs"

                  type="Microsoft.CSharp.CSharp3CodeProvider, CSharp3CodeDomProvider"/>

      </compilers>

    </system.codedom>

 

Note that the C# 3.0 compiler and CodeDOM provider can run side-by-side with the C# 2.0 versions (so you don’t have to worry about it breaking VS or ASP.NET when you install it). 

 

Step 1: Creating your first ASP.NET page using LINQ

 

Create a new page called Step1.aspx.  Within the .aspx page add a GridView control like so:

 

<%@ Page Language="C#" CodeFile="Step1.aspx.cs" Inherits="Step1" %>

 

<html>

<body>

    <form id="form1" runat="server">

    <div>

   

        <h1>City Names</h1>

   

        <asp:GridView ID="GridView1" runat="server">

        </asp:GridView>

   

    </div>

    </form>

</body>

</html>

 

Within the code-behind file we’ll then write the canonical “hello world” LINQ sample – which involves searching and ordering a list of strings:


using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Query;

 

public partial class Step1 : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        string[] cities = { "London", "Amsterdam", "San Francisco", "Las Vegas",

                            "Boston", "Raleigh", "Chicago", "Charlestown",

                            "Helsinki", "Nice", "Dublin" };

 

        GridView1.DataSource = from city in cities

                               where city.Length > 4

                               orderby city

                               select city.ToUpper();

 

        GridView1.DataBind();

    }

}

 

In the above sample I’ve created an array of strings listing the cities I’ve visited from Jan->May of this year.   I’m then using a LINQ query expression against the array.  This query expression returns all cities where the city name is greater than 4 characters, and orders the result in alphabetical order and transforms those city names into upper case.

 

LINQ queries return results of type: IEnumerable<T> -- where <T> is determined by the object type of the “select” clause.  In the above sample “city” is a string, so the type-safe result is a generics based collection like so:

 

        IEnumerable<string> result = from city in cities

                                     where city.Length > 4

                                     orderby city

                                     select city.ToUpper();

 

Because ASP.NET controls already support databinding to any IEnumerable collection, we can easily assign this LINQ query result to the GridView and call its DataBind() method to generate this page output result:

 

 

Note that instead of using the GridView control I could have just as easily used the <asp:repeater>, <asp:datalist>, <asp:dropdownlist>, or any other ASP.NET list control (both those built-into the product or ones built by other developers).  For the purposes of these samples I’m just going to use the <asp:gridview> -- but again know that you can use any. 

 

Step2: Using Richer Collections

 

Searching an array of strings is not terribly interesting (although sometimes actually useful).  More interesting would be the ability to search and work against richer collections of our own making.  The good news is that LINQ makes this easy.  For example, to better track trips I can create a simple class called “Location” in my project below:

 

using System;

 

public class Location

{

    // Fields

    private string _country;

    private int    _distance;

    private string _city;

 

    // Properties

    public string Country

    {

        get { return _country; }

        set { _country = value; }

    }

 

    public int Distance

    {

        get { return _distance; }

        set { _distance = value; }

    }

 

    public string City

    {

        get { return _city; }

        set { _city = value; }

    }

}

 

This exposes 3 public properties to track the County, City name and Distance from Seattle.  I can then create a Step2.aspx file with a GridView control that defines 3 columns like so:

 

<%@ Page Language="C#" CodeFile="Step2.aspx.cs" Inherits="Step2" %>

 

<html>

<body>

    <form id="form1" runat="server">

 

    <h1>Cities and their Distances</h1>

   

    <asp:GridView ID="GridView1" AutoGenerateColumns="false" runat="server">

       <Columns>

          <asp:BoundField HeaderText="Country" DataField="Country" />

          <asp:BoundField HeaderText="City" DataField="City" />

          <asp:BoundField HeaderText="Distance from Seattle" DataField="Distance" />

       </Columns>

    </asp:GridView>

   

    </form>

</body>

</html>

 

I can then populate a collection of Location objects and databind it to the Grid in my code-behind like so:

 

using System;

using System.Collections.Generic;

using System.Web;

using System.Query;

 

public partial class Step2 : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        List<Location> cities = new List<Location>{

                                    new Location { City="London", Distance=4789, Country="UK" },

                                    new Location { City="Amsterdam", Distance=4869, Country="Netherlands" },

                                    new Location { City="San Francisco", Distance=684, Country="USA" },

                                    new Location { City="Las Vegas", Distance=872, Country="USA" },

                                    new Location { City="Boston", Distance=2488, Country="USA" },

                                    new Location { City="Raleigh", Distance=2363, Country="USA" },

                                    new Location { City="Chicago", Distance=1733, Country="USA" },

                                    new Location { City="Charleston", Distance=2421, Country="USA" },

                                    new Location { City="Helsinki", Distance=4771, Country="Finland" },

                                    new Location { City="Nice", Distance=5428, Country="France" },

                                    new Location { City="Dublin", Distance=4527, Country="Ireland" }

                                };

 

        GridView1.DataSource = from location in cities

                               where location.Distance > 1000

                               orderby location.Country, location.City

                               select location;

 

        GridView1.DataBind();

    }

}

 

The above code-behind shows off a few cool features.  The first is the new C# 3.0 support for creating class instances, and then using a terser syntax for setting properties on them: 

 

new Location { City="London", Distance=4789, Country="UK" }

 

This is very useful when instantiating and adding classes within a collection like above (or within an anonymous type like we’ll see later).  Note that rather than use an array this time, I am using a Generics based List collection of type “Location”.  LINQ supports executing queries against any IEnumerable<T> collection, so can be used against any Generics or non-Generics based object collections you already have. 

 

For my LINQ query I’m then returning a collection of all cities that are more than 1000 miles away from Seattle.  I’ve chosen to order the result in alphabetical order – first by country and then by city name.  The result of this LINQ query is again dictated by the type of the “location” variable – so in this case of type “Location”:

 

        IEumerable<Location> result = from location in cities

                                      where location.Distance > 1000

                                      orderby location.Country, location.City

                                      select location;

 

When I databind this result against the GridView I get a result like so:

 

 

Step 3: Refactoring the City Collection Slightly

 

Since we’ll be re-using this collection of cities in several other samples, I decided to encapsulate my travels in a “TravelOrganizer” class like so:

 

using System;

using System.Collections.Generic;

 

public class TravelOrganizer

{

    public List<Location> PlacesVisited

    {

        get

        {

            List<Location> cities = new List<Location>{

                                        new Location { City="London", Distance=4789, Country="UK" },

                                        new Location { City="Amsterdam", Distance=4869, Country="Netherlands" },

                                        new Location { City="San Francisco", Distance=684, Country="USA" },

                                        new Location { City="Las Vegas", Distance=872, Country="USA" },

                                        new Location { City="Boston", Distance=2488, Country="USA" },

                                        new Location { City="Raleigh", Distance=2363, Country="USA" },

                                        new Location { City="Chicago", Distance=1733, Country="USA" },

                                        new Location { City="Charleston", Distance=2421, Country="USA" },

                                        new Location { City="Helsinki", Distance=4771, Country="Finland" },

                                        new Location { City="Nice", Distance=5428, Country="France" },

                                        new Location { City="Dublin", Distance=4527, Country="Ireland" }

                                    };

 

            return cities;

        }

    }

}

 

This allows me to then just write the below code in our code-behind to get the same result as before:

 

using System;

using System.Collections.Generic;

using System.Web;

using System.Web.UI;

using System.Query;

 

public partial class Step3 : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        TravelOrganizer travel = new TravelOrganizer();

 

        GridView1.DataSource = from location in travel.PlacesVisited

                               where location.Distance > 1000

                               orderby location.Country, location.City

                               select location;

 

        GridView1.DataBind();

    }

}

 

What is really cool about LINQ is that it is strongly-typed.  What this means is that:

 

1) You get compile-time checking of all queries.  Unlike SQL statements today (where you typically only find out at runtime if something is wrong), this means you will be able to check during development that your code is correct (for example: if I wrote “distanse” instead of “distance” above the compiler would catch it for me).

 

2) You will get intellisense within VS (and the free Visual Web Developer) when writing LINQ queries.  This makes both typing faster, but also make it much easier to work against both simple and complex collection and datasource object models.

 

Step 4: Skipping and Taking using .NET Standard Query Operators

 

LINQ comes with built-in support for many built-in Standard Query Operators.  These can be used within code by adding a “using System.Query” statement at the top of a class file, and can be applied to any sequence of data.   For example, if I wanted to list cities in order of distance and list the 2nd->6th farthest away cities I could write my code-behind file like so:

 

using System;

using System.Web.UI;

using System.Query;

 

public partial class Step4 : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        TravelOrganizer travel = new TravelOrganizer();

 

        GridView1.DataSource = (from location in travel.PlacesVisited

                               orderby location.Distance descending

                               select location).Skip(1).Take(5);

 

        GridView1.DataBind();

    }

}

 

Note how I am ordering the result by the distance (farthest to least).  I am then using the “Skip” operator to skip over the first city, and the "Take" operator to only return the remaining 5. 

 

What is really powerful is that the .NET Standard Query Operators are not a hard-coded list, and can be added to and replaced by any developer.  This enables very powerful domain specific implementations.  For example, when the Skip() and Take() operators are used with DLINQ – it translates the calls into back-end SQL logic that performs server-side paging (so that only a few rows are returned from the SQL database – regardless of whether it is from a table with 100,000+ rows of data).  This means that you will be able to trivially build efficient web data paging over lots of relational data (note: until then you can use the techniques listed here). 

 

Step 5: More Fun with .NET Standard Query Operators

 

In addition to returning sequences of data, we can use .NET Standard Query Operators to return single or computed results of data.  The below samples show examples of how to-do this:

 

<%@ Page Language="C#" CodeFile="Step5.aspx.cs" Inherits="Step5" %>

 

<html>

<body>

    <form id="form1" runat="server">

    <div>

        <h1>Aggregate Value Samples</h1>

       

        <div>

            <b>Farthest Distance City:</b>

            <asp:Label ID="MaxCityNameTxt" runat="server" Text="Label"></asp:Label>

            <asp:Label ID="MaxCityDistanceTxt" runat="server" Text="Label"></asp:Label>

        </div>

       

        <div>

            <b>Total Travel Distance (outside of US):</b>

            <asp:Label ID="TotalDistanceTxt" runat="server" Text="Label"></asp:Label>

        </div>       

       

        <div>

            <b>Average Distance:</b>

            <asp:Label ID="AverageDistanceTxt" runat="server" Text="Label"></asp:Label>

        </div>       

       

    </div>

    </form>

</body>

</html>

 

Step5.aspx.cs code-behind file:

 

using System;

using System.Collections.Generic;

using System.Web.UI;

using System.Query;