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 = {
"
"
"
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
<%@
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
</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="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="Nice", Distance=5428, Country="
new
Location { City="
};
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="
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
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="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="
new
Location { City="Nice", Distance=5428, Country="
new
Location { City="
};
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
<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;
public
partial
class
Step5 :
System.Web.UI.Page
{
protected
void Page_Load(object
sender, EventArgs e)
{
TravelOrganizer travel =
new
TravelOrganizer();
//
// Calculate farthest city away
Location farthestCity =
(from location
in
travel.PlacesVisited
orderby location.Distance descending
select location).First();
MaxCityNameTxt.Text = farthestCity.City;
MaxCityDistanceTxt.Text =
"(" +
farthestCity.Distance +
" miles)";
//
// Calculate total city distances of all cities outside
US
int totalDistance =
(from location
in
travel.PlacesVisited
where
location.Country !=
"
select location).Sum(loc => loc.Distance);
TotalDistanceTxt.Text = totalDistance +
" miles";
//
// Calculate average city distances of each city
trip
double averageDistance =
travel.PlacesVisited.Average(loc => loc.Distance);
AverageDistanceTxt.Text = averageDistance +
" miles";
}
}
Note that the last two examples above use the new
Lambda Expression
support – which enable fragments of code (like
delegates) that can operate on top of data to compute a
result. You can
build your own .NET Query Operators that use these (for
example: you could build domain specific ones to
calculate shipping costs or payroll tax). Everything is strongly-typed, and will support
intellisense and compilation checking support.
The output of the above sample looks like so:
Step 6: Anonymous Types
One of the new C# and VB language features that LINQ
can take advantage of is support for “Anonymous
Types”. This
allows you to easily create and use type structures
inline without having to formally declare their object
model (instead it can be inferred by the initialization
of the data). This is very useful to “custom shape” data with LINQ
queries.
For example, consider a scenario where you are working
against a database or strongly-typed collection that has
many properties – but you only really care about a few
of them. Rather
than create and work against the full type, it might be
useful to only return those properties that you
need. To see
this in action we’ll create a step6.aspx file like
so:
<%@
Page
Language="C#"
AutoEventWireup="true"
CodeFile="Step6.aspx.cs"
Inherits="Step6"
%>
<html>
<body>
<form
id="form1"
runat="server">
<div>
<h1>Anonymous Type</h1>
<asp:GridView
ID="GridView1"
runat="server">
</asp:GridView>
</div>
</form>
</body>
</html>
And within our code-behind file we’ll write a LINQ
query that uses anonymous types like so:
using
System;
using
System.Web.UI;
using
System.Query;
public
partial
class
Step6 :
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.City
select new {
City = location.City,
Distance = location.Distance
};
GridView1.DataBind();
}
}
Note that instead of returning a “location” from our
select clause like before, I am instead creating a new
anonymous type that has two properties – “City” and
“Distance”. The
types of these properties are automatically calculated
based on the value of their initial assignment (in this
case a string and an int), and when databound to the
GridView produce an output like so:
Step 7: Anonymous Types (again)
The previous sample showed a basic example of using
anonymous types to custom-shape the output of a LINQ
query. The below
sample provides a richer and more practical
scenario. It
transforms our list of cities into a hierarchical result
collection – where we group the results around countries
using an anonymous type that we define that contains the
country name, a sub-collection list of city details, and
the sum of the total distance of all cities within the
country (computed using a lambda expression like we
demonstrated in step5 above):
using
System;
using
System.Web.UI;
using
System.Query;
public
partial
class
Step7 :
System.Web.UI.Page
{
protected
void Page_Load(object
sender, EventArgs e)
{
TravelOrganizer travel =
new
TravelOrganizer();
GridView1.DataSource = from location
in
travel.PlacesVisited
group location by location.Country into loc
select new {
Country = loc.Key,
Cities = loc,
TotalDistance = loc.Sum(dist => dist.Distance)
};
GridView1.DataBind();
}
}
The GridView on our .aspx page is then defined like
so:
<%@
Page
Language="C#"
AutoEventWireup="true"
CodeFile="Step7.aspx.cs"
Inherits="Step7"
%>
<html>
<body>
<form
id="form1"
runat="server">
<div>
<h1>Groupings with
Anonymous Classes</h1>
<asp:GridView
ID="GridView1"
AutoGenerateColumns="false"
runat="server">
<Columns>
<asp:BoundField
HeaderText="Country"
DataField="Country"
/>
<asp:TemplateField
HeaderText="Cities">
<ItemTemplate>
<asp:BulletedList
ID="BulletedList1"
runat="server"
DataSource='<%#Eval("Cities")%>' DataValueField="City"/>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField
HeaderText="Total Distance"
DataField="TotalDistance"
/>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
Notice how I’ve added a GridView templatefield column
for the “Cities” column – and within that I’ve then
added an <asp:bulletedlist> control (a new control
built-in with ASP.NET 2.0) that databinds its values
from the cities property of the hierarchical result we
created using our LINQ query above. This generates output like so:
Note that all of the databind syntax and hierarchical
binding support in the .aspx page above is fully
supported in ASP.NET 2.0 today – so you can use this
same technique with any existing app you have now. What is new (and I think very cool) is the data shaping
capabilities provided by anonymous types and LINQ –
which makes binding hierarchical data against ASP.NET
controls very easy.
Next Steps
All of my samples above were against in-memory
collections. They show you how you will be able to use LINQ against
any .NET object model (includes all the ones you have
already).
In my next few LINQ-related blog postings I’ll show how
you can go even further, and take advantage of the new
DLINQ support to use the above techniques against
relational databases as well as the new XLINQ support to
work against XML files and structures. What is great about the LINQ project is that the syntax
and concepts are the same across all of its uses – so
once you learn how to use LINQ against an array or
collection, you also know all the concepts needed to
work against a database or even XML file.
For example, if you use DLINQ to generate a Northwinds
database mapping of Suppliers and their Products (no
code is required to set this up), the below code is all
you need to write to obtain and databind a hierarchical
database result against a GridView like we did above
(note: we are using the same data-shaping technique as
our previous sample to only require fetching two columns
from the database, and automatically join the products
of each supplier as a hierarchical group result):
using
System;
using
System.Query;
public
partial
class
Data_Data2 :
System.Web.UI.Page
{
protected
void Page_Load(object
sender, EventArgs e)
{
Northwind
db = new Northwind();
GridView1.DataSource = from x
in db.Suppliers
where x.Country ==
"
orderby x.Country
select new {
x.CompanyName,
x.Country,
x.Products
};
GridView1.DataBind();
}
}
No custom SQL syntax or code is required – this is all
that needs to be written to efficiently fetch and
populate hierarchical data now (note: only the rows and
columns needed will be fetched -- DLINQ can use the
remote function support within LINQ so that it
does not need to materialize or fetch the full
database table or all columns from a row). And it is all type-safe, with full compiler checking,
intellisense, and debugging supported.
Even better, the ability to plug-in new LINQ providers
(of which DLINQ and XLINQ are just two examples) is
completely open – so developers who either build or use
existing data providers today (for example: O/R database
mappers) can easily integrate their implementations with
LINQ to have a seamless developer experience. Once you know LINQ you will know all the basics needed
to program against any of them.
Summary
Hopefully this provides a glimpse of some of the cool
new things coming. You can try it all out today by downloading the May CTP
drop of LINQ today from here. You can also
download and run all of the samples I built above from
this .ZIP file here.
Hope this helps,
Scott