New C# and VB.NET Language Features
To
get LINQ to SQL to work, Microsoft had to introduce several new
language features to both C# and VB.NET. Many of these features make C#
and VB.NET behave more like a dynamic language (think JavaScript).
Although the primary motivation for introducing these new features was
to support LINQ, the new features are also interesting in their own
right.
To use
these new language features, you’ll need to make sure your website is
targeting .NET Framework 3.5. Ensure that you have a web.config file in
your project. Next, select the menu option Website, Start Options and
then select the Build tab. For Target Framework, select .NET Framework
3.5. Performing these steps will modify your web.config file so that it
references the necessary assemblies and uses the right version of the C#
or VB.NET compiler.
Understanding Automatic Properties
The first of these new language features we will explore is called automatic properties.
Unfortunately, this feature is supported only by C# and not VB.NET.
Automatic properties provide you with a shorthand method for defining a
new property.
You can’t
add any logic to the Getters and Setters for an automatic property. You
also can’t create read-only automatic properties. Why are automatic
properties relevant to LINQ to SQL? When working with LINQ to SQL, you
often use classes to represent nothing more than the list of columns you
want to retrieve from the database (the shape of the data) like the
select list in a SQL query. In those cases, you just want to do the
minimum amount of work possible to create a list of properties, and
automatic properties allow you to do this. You can quickly add an
automatic property to a class or page when using Visual Web
Developer/Visual Studio by typing prop and hitting the Tab key twice.
Understanding Initializers
You can use initializers to reduce the amount of work it takes to create a new instance of a class.
Understanding Type Inference
Here’s
a new feature that makes C# and VB.NET look much more like a dynamic
language such as JavaScript: local variable type inference. When you
take advantage of type inference, you allow the C# or VB.NET compiler to
determine the type of a variable at compile time.
Here’s an example of how you use type inference with C#:
var message = “Hello World!”;
And here is how you would use type inference with VB.NET:
Dim message = “Hello World!”
Notice that
the message variable is declared without specifying a type. The C# and
VB.NET compilers can infer the type of the variable (it’s a String) from
the value you use to initialize the variable. No performance impact
results from using type inference (the variable is not late bound). The
compiler does all the work of figuring out the data type at compile
time. Notice that a new keyword has been introduced into C# to support
type inference: the var keyword. You declare a variable as type var when
you want the compiler to figure out the variable’s data type all by
itself.
You can
take advantage of type inference only when you provide a local variable
with an initial value. For example, this won’t work (C#):
var message;
message = “Hello World!”;
The C#
compiler will refuse to compile this code because the message variable
is not initialized when it is declared. The following code will work in
VB.NET (but it won’t do what you want):
Dim message
message = “Hello World!”
In this
case, VB.NET will treat the message variable as type Object. At runtime,
it will cast the value of the variable to a string when you assign the
string to the variable. This is not good from a performance perspective.
VB.NET 9.0 includes a new option called Option Infer. Option Infer must
be enabled in order for the implicit typing feature to work. You can
enable it for a particular class file by adding the line Option Infer On
at the very top of a code file.
The
relevance of type inference to LINQ to SQL will be apparent after you
read the next section. In many circumstances when using LINQ to SQL, you
won’t actually know the name of the type of a variable, so you have to
let the compiler infer the type.
Understanding Anonymous Types
Anonymous
types is another idea that might be familiar to you from dynamic
languages. Anonymous types are useful when you need a transient,
fleeting type and you don’t want to do the work to create a class.
Here’s an example of creating an anonymous type in C#:
var customer = new {FirstName = “Stephen”, LastName = “Walther”};
Here’s how you would create the same anonymous type in VB.NET:
Dim customer = New With {.FirstName = “Stephen”, .LastName = “Walther”}
Notice that
the customer variable is used without specifying a type (this looks
very much like JavaScript or VBScript). However, it is important to
understand that customer does have a type, you just don’t know its name:
It’s anonymous. In a single line of code, we’ve managed to both create a
new class and initialize its properties. The terseness brings tears to
my eyes.
Anonymous
types are useful when working with LINQ to SQL because you’ll discover
that you’ll often need to create new types on the fly. For example, you
might want to return a class that represents a limited set of database
columns when performing a particular query. You’ll need to create a
transient class that represents the columns.
Understanding Generics
Yes,
I realize that generics are not new to .NET 3.5. However, they are such
an important aspect of LINQ to SQL that it is worth using a little
space to review this feature. To use generics, you need to import the
System.Collections.Generic namespace. I most often use generics by
taking advantage of generic collections. For example, if you want to
represent a list of strings, you can declare a list of strings like this
(in C#):
List<string> stuffToBuy = new List<string>();
stuffToBuy.Add(“socks”);
stuffToBuy.Add(“beer”);
stuffToBuy.Add(“cigars”);
Here’s how you would declare the list of strings in VB.NET:
Dim stuffToBuy As New List(Of String)
stuffToBuy.Add(“socks”)
stuffToBuy.Add(“beer”)
stuffToBuy.Add(“cigars”)
And,
by taking advantage of collection initializers, you can now declare a
strongly typed list of strings in a single line like this (in C#):
List<string> stuffToBuy2 = new List<string> {“socks”, “beer”, “cigars”};
Unfortunately, VB.NET does not support collection intializers or array initializers.
The List
class is an example of a generic because you specify the type of object
that the class will contain when you declare the List class. In C#, you
specify the type in between the alligator mouths (< >), and in
VB.NET you use the Of keyword. Alternatively, we could have created
a List class that contains integers or a custom type such as products
or customers represented by a Product or Customer class. A generic
collection like a List is superior to a nongeneric collection like an
ArrayList because a generic is strongly typed. An ArrayList stores
everything as an object. A generic stores everything as a particular
type. When you pull an item out of an ArrayList, you must cast it to a
particular type before you use it. An item pulled from a generic, on the
other hand, does not need to be cast to a type. Generics are not
limited solely to collections. You can create generic methods, generic
classes, and generic interfaces.
Understanding Lambda Expressions
Lambda
expressions, another new language feature introduced with .NET
Framework 3.5, provide you with an extremely terse way of defining
methods. Imagine, for example, that you want to programmatically wire up
a Click event handler to a button control. Listing 18.6 is an example
of one way of doing this. Lambda expressions take the notion of the
anonymous method one step further. Lambda expressions reduce the amount
of syntax required to define a method to its semantic minimum.
Understanding Extension Methods
The
idea behind extension methods should also be familiar to anyone who has
worked with JavaScript (think prototype). By taking advantage of
extension methods, you can add new methods to existing classes. For
example, you can make up any method you want and add the method to the
String class. I’m constantly HTML-encoding strings because I am paranoid
about JavaScript injection attacks. In .NET Framework 2.0, you
HTML-encode a string by calling the Server.HtmlEncode() static method,
like this:
string evilString = “<script>alert(‘boom!’)<” + “/script>”;
ltlMessage.Text = Server.HtmlEncode(evilString);
In this statement, the static HtmlEncode() method is called on the Server class. Wouldn’t
it be nice if we could just call HtmlEncode() on a string directly like this:
string evilString = “<script>alert(‘boom!’)<” + “/script>”;
ltlMessage.Text = evilString.HtmlEncode();
Using
extension methods, we can do exactly that. We can add any methods to a
class that we feel like. You create an extension method by creating a
static class and declaring a static method that has a special first
parameter.
Understanding LINQ
Finally,
we get to the topic of LINQ—the last topic we need to examine before we
can dive into the true subject of this chapter: LINQ to SQL. LINQ
stands for Language Integrated Query. LINQ consists of a set of new
language features added to both the C# and VB.NET languages that enable
you to perform queries. LINQ enables you to use SQL query–like syntax
within C# or VB.NET.
Here’s a simple example of a LINQ query:
var words = new List<string> {“zephyr”, “apple”, “azure”};
var results = from w in words
where w.Contains(“z”)
select w;
The first
statement creates a generic List of three strings named “words.” The
second statement is the LINQ query. The LINQ query resembles a backward
SQL statement. It retrieves all the words from the List that contain the
letter z. After you execute the query, the results variable will contain the following list of two words:
zephyr
azure
You can
perform a standard LINQ query against any object that implements the
IEnumerable<T> interface. An object that implements this interface
is called a sequence. Notable examples of sequences are both the
generic List class and the standard Array class (so anything you can
dump into an array, you can query with LINQ). The C# language supports
the following clauses that you can use in a query:
- from—Enables you to specify the data source and a variable for iterating over the data source (a range variable).
- where—Enables you to filter the results of a query.
- select—Enables you to specify the items included in the results of the query.
- group—Enables you to group related values by a common key.
- into—Enables you to store the results of a group or join into a temporary variable.
- orderby—Enables you to order query results in ascending or descending order.
- join—Enables you to join two data sources using a common key.
- let—Enables you to create a temporary variable to represent subquery results.
Building a LINQ
query is like building a backward SQL query. You start by specifying a
from clause that indicates where you want to get your data. Next,
optionally, you specify a where clause that filters your data. Finally,
you specify a select clause that gives shape to your data (determines
the objects and properties you want to return). Under the covers,
standard LINQ queries are translated into method calls on the
System.Linq.Enumerable class. The Enumerable class contains extension
methods that are
applied to any class that implements the IEnumerable<T> interface.
So, the query
var results = from w in words
where w.Contains(“z”)
select w;
is translated into this query by the C# compiler:
var results = words.Where( w => w.Contains(“z”) ).Select( w => w );
The first query uses query syntax and the second query uses method syntax.
The two queries are otherwise identical. Notice that the query using
method syntax accepts lambda expressions for its Where() and Select()
methods. The lambda expression used with the Where() method filters the
results so that only words that contain the letter z are returned. The Select() method indicates the object and property to return. If we had passed the lambda expression
w => w.Length to the Select() method, the query would return the length of each word instead of the word itself.
The choice
of whether to use query or method syntax when building LINQ queries is
purely a matter of preference. Query syntax uses language-specific
syntax (C# or VB.NET). Method syntax is language independent. I find
that I use method syntax more than query syntax because query syntax is a
subset of method syntax. In other words, you can do more with method
syntax. That said, in some cases, writing a query in method syntax is
just too verbose. For example, writing left outer joins with LINQ to SQL
is much easier using query syntax than method syntax. At the end of the
day, the choice of whether to use method or query syntax doesn’t really
matter because all the query syntax statements get translated by the
compiler into method syntax. In the case of standard LINQ, those method
calls are calls on methods of the Enumerable class. Lookup the
System.Linq.Enumerable class in the SDK documentation to view the full
list of methods that the Enumerable class supports. Here is a list of
some of the more interesting
and useful methods:
- Aggregate()—Enables you to apply a function to every item in a sequence.
- Average()—Returns the average value of every item in a sequence.
- Count()—Returns the count of items from a sequence.
- Distinct()—Returns distinct items from a sequence.
- Max()—Returns the maximum value from a sequence.
- Min()—Returns the minimum value from a sequence.
- Select()—Returns certain items or properties from a sequence.
- Single()—Returns a single value from a sequence.
- Skip()—Enables you to skip a certain number of items in a sequence and return the remaining elements.
- Take()—Enables you to return a certain number of elements from a sequence.
- Where()—Enables you to filter the elements in a sequence.
Creating LINQ to SQL Entities
LINQ
to SQL enables you to perform LINQ queries against database data.
Currently, you can use LINQ to SQL with Microsoft SQL Server 2000 or
Microsoft SQL Server 2005 (including the SQL Server Express editions).
Other databases—such as Oracle, DB2, and Access databases—might be
supported in the future, but they are not right now.
To
use LINQ to SQL, you need to add a reference to the
System.Data.Linq.dll assembly. Select the menu option Website, Add
Reference and, beneath the .NET tab, select System.Data.Linq.dll.
Performing this action will add a new assembly reference to the
<assemblies> section of your web.config file. If you use the
Object Rational Designer, this reference is added automatically.
In this section, you learn how to create LINQ to SQL entities. An entity is
a C# or VB.NET class that represents a database table (or view). You
can use a set of standard custom attributes to map classes and
properties to tables and columns. You learn how to create entities both
by hand and by using the Object Rational Designer.
Building Entities by Hand
Before
you can start performing queries using LINQ to SQL, you need to create
one or more entity classes that represent the data you are querying. In
this section, you learn how to code these classes by hand.
The Column
and Table attribute classes live in the System.Data.Linq.Mapping
namespace. Furthermore, notice that the class itself is decorated with a
Table attribute. This attribute marks the class as representing a
database table. The Column attribute supports the following properties:
- AutoSync—Indicates whether the value of the property is synchronized with the value of the database column automatically. Possible values are OnInsert, Always, and None.
- CanBeNull—Indicates whether the property can represent a null value.
- DbType—Indicates the database column data type.
- Expression—Indicates the expression used by a computed database column.
- IsDbGenerated—Indicates that the value of the property is generated in the database (for example, an identity column).
- IsDiscriminator—Indicates whether the property holds the discriminator value for an inheritance hierarchy.
- IsPrimaryKey—Indicates whether the property represents a primary key column.
- IsVersion—Indicates whether the property represents a column that represents a row version (for example, a timestamp column).
- Name—Indicates the name of the database column that corresponds to the property.
- Storage—Indicates a field where the value of the property is stored.
- UpdateCheck—Indicates whether the property participates in optimistic concurrency comparisons.
The Table attribute supports the following single property:
- Name—Indicates the name of the database table that corresponds to the class.
Some comments
about these attributes are needed. First, you don’t need to specify a
Name property when your property or class name corresponds to your
database column or table name. If, on the other hand, your database
table were named Movies and your class were named Movie, you would need
to supply the Name property for the Table attribute to map the correct
table to the class. Second, you always want to specify the primary key
column by using the IsPrimaryKey property. For example, if you don’t
specify a primary key column, you can’t do updates against your database
using LINQ.
Finally,
even though we didn’t do this in our Movie class, you almost always want
to include a timestamp column in your database table and indicate the
timestamp column by using the IsVersion property. If you don’t do this,
LINQ to SQL will check whether the values of all the properties match
the values of all the columns before performing an update command to
prevent concurrency conflicts. If you specify a version property, LINQ
to SQL can check the value of this single property against the database
rather than all the columns.
Building Entities with the Object Relational Designer
As
an alternative to building entities by hand, you can use the Object
Relational Designer. You can simply drag database tables from the
Database Explorer (Server Explorer) onto the Designer. The Designer
generates the entity classes with the correct attributes automatically.
Follow these steps to use the Object Relational Designer:
1. Select the menu option Website, Add New Item to open the Add New Item dialog box.
2. Select the LINQ to SQL Classes template, give it the name MyDatabase, and click the Add button.
3. When prompted to create the LINQ to SQL classes in the App_Code folder, click the Yes button.
4.
After the Object Relational Designer opens, drag one or more database
tables from the Database Explorer/Server Explorer window onto the
Designer surface.
You can
view the code that the Designer generates by expanding the
MyDatabase.dbml node in the App_Code folder and double-clicking the
MyDatabase.designer.cs file. The Designer generates a strongly typed
DataContext class named MyDatabaseContext. Each database table that you
drag onto the Designer surface gets exposed by the DataContext class as a
strongly typed property. The Designer, furthermore, generates a
distinct class for each database table you drag onto the Designer. For
example, after you drag the Movie table onto the Designer, a new class
named Movie is created in the MyDatabase.designer.cs file. The Object
Relational Designer attempts to pluralize table names automatically when
you add them to the Designer. So, when you drag the Movie table onto
the Designer, the Designer generates a DataContext property named
Movies. Most of the time, but not all of the time, it gets the
pluralization right. You can turn off this feature by selecting the menu
option Tools, Options and selecting the Database Tools, O/R Designer
tab.
Building Entity Associations
One
entity can be associated with another entity. For example, a
MovieCategory entity might be associated with one or more Movie
entities. If you have defined foreign key relationships between your
database tables, these relationships are preserved when you drag your
tables onto the Object Relational Designer. The Object Relational
Designer will generate entity associations based on the foreign key
relationships automatically.
For
example, the MovieCategory entity is related to the Movie entity through
the Movie entity’s CategoryId property. As long as you have defined a
foreign key relationship between Movie.CategoryId and MovieCategory.Id,
you can use a query like this following:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var category = db.MovieCategories.Single( c => c.Name == “Drama” );
var query = category.Movies;
The second
statement grabs the Drama movie category. The third statement returns
all movies associated with the Drama movie category. In this case, we’ve
followed a one-tomany relationship and got a list of movies that match a
movie category.
You can also go the opposite direction and retrieve the one and only movie category that matches a particular movie:
string categoryName = db.Movies.Single(m=>m.Id==1).MovieCategory.Name;
This query retrieves the name of the movie category associated with the movie that has an ID of 1.
Under the
covers, the Object Relational Designer creates the entity relationships
by adding association attributes to entity properties. The Object
Relational Designer also adds some tricky synchronization logic to keep
the properties of associated entities synchronized. Although I wish that
I could code all my entities by hand, adding all the logic necessary to
get the entity associations to work correctly is too much work. For
that reason, I use the Object Relational Designer.
Using the LinqDataSource Control
I
want to briefly describe the LinqDataSource control. You can use this
control to represent LINQ queries. For example, the page in Listing
18.16 contains a simple search form for searching movies by director.
The page uses a LinqDataSource to represent the LINQ query.
Performing Standard Database Commands with LINQ to SQL
In
this section, you learn how to use LINQ to SQL as a replacement for
working directly with SQL. We’ll start by discussing how LINQ to SQL
queries differ from standard LINQ queries. Next, we’ll examine how you
can perform standard database queries and commands using LINQ to SQL
such as Select, Update, Insert, and Delete commands. We’ll also discuss
how you can create dynamic queries with LINQ. Finally, we’ll investigate
the very important topic of how you can debug LINQ to SQL queries.
LINQ to Objects versus LINQ to SQL
You
can use standard LINQ (LINQ to Objects) with any object that implements
the IEnumerable<T> interface. You can use LINQ to SQL, on the
other hand, with any object that implements the IQueryable<T>
interface. Standard LINQ is implemented with the extension methods
exposed by the System.Linq.Enumerable class. LINQ to SQL, on the other
hand, uses the extension methods exposed by the System.Linq.Queryable
class.
Why the difference?
When
you build a query using standard LINQ, the query executes immediately.
When you build a query using LINQ to SQL, on the hand, the query does
not execute until you start enumerating the results. In other words, the
query doesn’t execute until you use a foreach loop to walk through the
query results.
Selecting with LINQ to SQL
If
you want to perform a simple, unordered select, you can use the
following query (assuming that you have an entity named Movie that
represents the Movie database table):
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = db.Movies;
Notice that
no LINQ extension methods are used in this query. All the items are
retrieved from the Movies table. If you prefer, you can use query syntax
instead of method syntax, like this:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = from m in db.Movies select m;
Selecting Particular Columns
If
you want to select only particular columns, and not all the columns,
from a database table, you can create an anonymous type on the fly, like
this:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = db.Movies.Select( m => new {m.Id, m.Title} );
The
expression new {m.Id, m.Title} creates an anonymous type that has two
properties: Id and Title. Notice that the names of the properties of the
anonymous type are inferred. If you want to be more explicit, or if you
want to change the names of the anonymous type’s properties, you can
construct your query like this:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = db.Movies.Select( m => new {Id = m.Id, MovieTitle = m.Title} );
Selecting
Particular Rows If you want to select only particular rows from a
database table and not all the rows, you can take advantage of the
Where() method. The following LINQ to SQL query retrieves all the movies
directed by George Lucas with box office totals greater than $100,000
dollars:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = db.Movies
.Where( m => m.Director == “George Lucas” && m.BoxOfficeTotals > 100000.00m)
.Select( m => new {m.Title, m.Director, m.BoxOfficeTotals});
Remember to
always call the Where() method before the Select() method. You need to
filter your data with Where() before you shape it with Select().
Selecting Rows in a Particular Order You can use the following methods
to control the order in which rows are returned from a LINQ to SQL
query:
- OrderBy()—Returns query results in a particular ascending order.
- OrderByDescending()—Returns query results in a particular descending order.
- ThenBy()—Returns query results using in an additional ascending order.
- ThenByDescending()—Returns query results using an additional descending order.
The OrderBy() and
OrderBy() methods return an IOrderedQueryable<T> collection
instead of the normal IQueryable<T> collection type. If you want
to perform additional sorting, you need to call either the ThenBy() or
ThenByDescending() method.
Selecting a Single Row
If you want to select a single row from the database, you can use one of the following two query methods:
- Single()—Selects a single record.
- SingleOrDefault()—Selects a single record or a default instance.
The first method
assumes there is at least one element to be returned (if not, you get an
exception). The second method returns null (for a reference type) when
no matching element is found.
Performing a LIKE Select
You
can perform the equivalent of a LIKE Select with LINQ to SQL in several
ways. First, you can use String methods such as Length, Substring,
Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim,
ToLower, ToUpper, LastIndexOf, PadRight, and PadLeft with LINQ to SQL
queries. For example, the following query returns all movies that start
with the letter t:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = db.Movies.Where(m=>m.Title.StartsWith(“t”));
Paging Through Records
Doing
database paging right when working with ADO.NET is difficult. The SQL
language is not designed to make it easy to retrieve a range of records.
Doing database paging using LINQ to SQL queries, on the other hand, is
trivial. You can take advantage of the following two query methods to
perform database paging:
- Skip()—Enables you to skip a certain number of records.
- Take()—Enables you to take a certain number of records.
Joining Records from Different Tables
You
can perform joins when selecting entities just like you can when
joining database tables. For example, imagine that you want to join the
Movie and MovieCategory tables on the CategoryId key. Assuming that you
have both a Movie and MovieCategory entity, you can use the following
query:
MyDatabaseDataContext db = new MyDatabaseDataContext();
var query = db.MovieCategories
.Join(db.Movies, c=>c.Id, m=>m.CategoryId, (c,m)=>new {c.Id,c.Name,m.Title});
Caching Records Getting
caching to work with LINQ to SQL is a little tricky. Remember that a
LINQ to SQL query represents a query expression and not the actual query
results. The SQL command is not executed, and the results are not
retrieved until you start iterating through the query results.
Inserting with LINQ to SQL
There
are two steps to adding and inserting a new record with LINQ to SQL.
First, you need to use the InsertOnSubmit() method to add an entity to
an existing table. Next, you call SubmitChanges() on the DataContext to
execute the SQL INSERT statement against the database.
Updating with LINQ to SQL
You can use the Attach() method to attach an entity back into a data context. There are three overloads of the Attach() method:
- Attach(Object)—Enables you to attach an unmodified entity to the data context.
- Attach(Object, Boolean)—Enables you to attach a modified entity to the data context. The second parameter represents whether or not the entity has been modified. To use this overload, the entity must have a version/timestamp property.
- Attach(Object, Object)—Enables you to attach a modified entity to the data context. The first parameter represents the modified entity. The second parameter represents the original entity.
Deleting with LINQ to SQL
You can delete an entity with LINQ to SQL by using code like the following:
MyDatabaseDataContext db = new MyDatabaseDataContext();
Movie movieToDelete = db.Movies.Single(m=>m.Id==1);
db.Movies.DeleteOnSubmit( movieToDelete );
db.SubmitChanges();