LINQ and the ADO.NET Entity Framework - Beginning Visual Basic (2012)

Beginning Visual Basic(2012)

Chapter 14

LINQ and the ADO.NET Entity Framework

What You Will Learn in This Chapter:

· What LINQ is and what its syntax looks like

· The different forms of LINQ that are available and when they are appropriate to use

· How to use the ADO.NET Entity Framework

· How to use the EntityDataSource control to access the ADO.NET Entity Framework

· How to use the ListView and DataPager controls

Wrox.com Code Downloads for this Chapter

You can find the wrox.com code downloads for this chapter on the Download Code tab at www.wrox.com/remtitle.cgi?isbn=1118311809. The code is in the Chapter 14 download.

Language-Integrated Query (LINQ) is the query language that is tightly integrated with the programming languages used in the .NET Framework. LINQ enables you to query data from within .NET programming languages in the same way that SQL enables you to query data in a database. In fact, the LINQ syntax has been modeled partially after the SQL language, making it easier for programmers familiar with SQL to get started with LINQ.

LINQ comes in a few different implementations, enabling you to access and query a wide variety of sources, including in-memory data, XML files, .NET DataSets, and databases from your VB.NET or C# code. In the next section you get a brief overview of the main LINQ pillars. The remainder of this chapter focuses on the LINQ syntax and on the ADO.NET Entity Framework (EF), a technology that enables you to work with databases without writing a lot of code. The ADO.NET Entity Framework uses LINQ a lot under the hood, so you get a good shot at practicing your new LINQ skills.

Introducing LINQ

LINQ enables you to query data from a wide variety of data sources, directly from your programming code. LINQ is to .NET programming what SQL is to relational databases. With straightforward, declarative syntax you can query collections for objects that match your criteria.

LINQ is not just an add-on that is part of the .NET Framework. On the contrary, LINQ has been designed and implemented as a true part of the programming languages in .NET. This means that LINQ is truly integrated into .NET, giving you a unified way to query data, regardless of where that data comes from. In addition, because it is integrated into the language and not in a certain project type, LINQ is available in all kinds of projects, including web applications, Windows Forms applications, Console applications, and so on. To help developers get familiar with LINQ, its syntax is closely modeled after SQL, the most popular query language for relational databases. This means that LINQ has keywords such as Select, From, and Where to get data from a data source.

To give you an idea of what a LINQ query looks like, here's a quick example that shows a list of Wrox authors whose names contain the capital letter S:

VB.NET

Dim authors As String() = New String() {"Hanselman, Scott", "Evjen, Bill",
                          "Haack, Phil", "Vieira, Robert", "Spaanjaars, Imar"}
Dim result = From author In authors 
             Where author.Contains("S") 
             Order By author
             Select author
For Each author In result
  Label1.Text += author + "<br />"
Next
 

C#

using System.Linq;
...
string[] authors = new string[] { "Hanselman, Scott", "Evjen, Bill", 
                   "Haack, Phil", "Vieira, Robert", "Spaanjaars, Imar" };
var result = from author in authors
             where author.Contains("S")
             orderby author
             select author;
 
foreach (var author in result)
{
  Label1.Text += author + "<br />";
}

Although the syntax used in this example is probably quite easy to follow, the example itself is really powerful. Given an array of strings containing author names, you can simply select all the authors whose names contain the capital letter S and order them in ascending order. It should come as no surprise that in this example, the Label control displays my name and that of Scott Hanselman because only those two names match the Where criterion. Notice how in C# the code imports the System.Linq namespace. This is necessary to bring the LINQ functionality into scope for your application. If you're finding that some keywords don't show up in IntelliSense or VS gives you compilation errors on your LINQ queries, check that you have this namespace imported at the top of your code file. In a VB website, this namespace is included by default.

Of course, this example is only the beginning. The different types of LINQ discussed in the following three sections enable you to create much more powerful queries against a wide variety of data sources.

Because LINQ is so powerful and has so much potential, it has been integrated into many different areas of the .NET Framework. The following sections introduce the different LINQ implementations.

LINQ to Objects

This is the purest form of language integration. With LINQ to Objects, you can query collections in your .NET applications as you saw in the previous example. You're not limited to arrays because LINQ enables you to query almost any kind of collection that exists in the .NET Framework.

LINQ to XML

LINQ to XML is the new .NET way to read and write XML. Instead of typical XML query languages like XSLT or XPath, you can now write LINQ queries that target XML directly in your application.

LINQ to ADO.NET

ADO.NET is the part of the .NET Framework that enables you to access data and data services like SQL Server and many other different kinds of data sources. ADO.NET is also used under the hood by the SqlDataSource control and is commonly used in “raw data access code”—code written in C# or VB.NET that connects to a database without using the declarative data controls. With LINQ to ADO.NET you can query database-related information sets, including LINQ to DataSet, LINQ to SQL, and LINQ to Entities.

LINQ to DataSet enables you to write queries against the DataSet, a class that represents an in-memory version of a database.

LINQ to SQL enables you to write object-oriented queries in your .NET projects that target Microsoft SQL Server databases. The LINQ to SQL implementation translates your queries into SQL statements, which are then sent to the database to perform typical CRUD operations. In the 3.5 version of this book, this entire chapter was devoted to LINQ to SQL. However, in the meantime, a lot has happened. Microsoft has indicated that it will no longer actively develop LINQ to SQL. It will remain part of the .NET Framework and Visual Studio for the foreseeable future, but Microsoft probably won't be adding new functionality to it. The reason for this is the great overlap in functionality with the Entity Framework (EF). Almost anything you can do in LINQ to SQL can be done in LINQ to Entities. However, this latter framework is a lot more powerful and offers many more features than LINQ to SQL. Because it's more powerful, the Entity Framework is preferred over LINQ to SQL and as such it's the main topic of this chapter.

For more information about the other types of implementations, check out the official LINQ homepage at http://bit.ly/18ypUj.

Introducing the ADO.NET Entity Framework

EF is an Object Relational Mapper (ORM) that supports the development of data-oriented software applications. With EF, you can take a bunch of database objects like tables and turn them into .NET objects that you can access in your code. You can then use these objects in queries or use them directly in data-binding scenarios. EF also enables you to do the reverse: design an object model first and then let EF create the necessary database structure for you.

Working with EF is pretty easy and quite flexible. Using a diagram designer, you drag and drop objects like tables from your database into your Entity model. The database objects you drop on the diagram become available as .NET objects. For example, if you drop the Review table on the diagram, you end up with a strongly typed Review class. You can create instances of this class using LINQ queries and other means, as you see later in this chapter.

Note

The ADO.NET Entity Framework is a large and complex topic by itself. There's a lot more to it than what I can cover in this chapter. For an in-depth look at EF, pick up a copy of the excellent book Programming Entity Framework, Second Edition by Julia Lerman.

When you drop more than one related database table on your diagram, the designer detects the relationships between the tables and then replicates these relationships in your object model. For example, if you had a Review instance created in code using some LINQ to Entities query (as you see later), you could access its Genre property, which in turn gives you access to properties like Name:

VB.NET

Label1.Text = myReview.Genre.Name
 

C#

Label1.Text = myReview.Genre.Name;

Similarly, you can access the associated Reviews collection for a specific genre, for example to bind it to a data-bound control:

VB.NET

Repeater1.DataSource = myGenre.Reviews
 

C#

Repeater1.DataSource = myGenre.Reviews;

Don't worry about the actual syntax right now. You see a lot more of it in the remainder of this chapter. What's important to take away from this section is that EF creates a layer between your .NET application and your SQL Server database. The Entity Designer takes care of most of the work for you, providing access to a clean object model that you can use in your application.

Mapping Your Data Model to an Object Model

With EF, you map database items such as tables, columns, and relationships in the database to objects and properties in an object model in your application. VS comes with great tools to make this mapping as easy as possible, as you see in the following exercise.

Try It Out: A Simple LINQ to Entities Example

In this Try It Out, you see how to add an ADO.NET Entity Data Model file to your project, add database tables to the model, and then write a simple LINQ query to access the data in the underlying tables.

1. Open the Planet Wrox project that you have been working on so far. Right-click the App_Code folder, choose Add ⊆ Add New Item, and select your programming language on the left. Then click ADO.NET Entity Data Model, type PlanetWrox as the name, and click Add to add the item to your project. If you don't see the item in the list, check that you right-clicked App_Code and not another folder like App_Data.

2. On the dialog box that follows, make sure that Generate from Database is selected and click Next.

3. In the Choose Your Data Connection step, make sure PlanetWroxConnectionString1 is selected in the drop-down and that the check box to store the settings in Web.config is checked. Your dialog now looks like Figure 14.1.

Click Next to go to the Choose Your Database Objects and Settings dialog box.

4. In this dialog box, expand Tables and then dbo, and then check off the Genre and Review tables. If you see a sysdiagrams table, leave it unchecked. This is a table used by SQL Server internally and you don't need it in your Planet Wrox model. If you're using an English version of VS, you get an option to pluralize or singularize names in the model automatically, which you should leave checked. For other languages you'll need to do this manually, as you see next. Finally, make sure you leave the option to include foreign key columns in the model checked. You see what that option is used for later in this chapter. Click Finish to add the model to your site. If you get a security warning, click Do Not Show This Message Again and then click OK. Visual Studio uses what's called a T4 template to generate the code for you and by default you need to grant permissions to execute this template.

5. VS adds a file called PlanetWrox.edmx and two files with a .tt extension and then opens the Entity Designer for you in the main editor window, shown in Figure 14.2. It also created a Bin folder in the root of your site and added an assembly and an XML documentation file needed by the Entity Framework.

This Entity Designer shows you .NET classes that have been generated based on the tables in your database. VS draws a line connecting the two classes, indicating it picked up the relationship that you created between the underlying tables in Chapter 12. If you don't see the line, or you don't see Reviews at the bottom of the Genre class or Genre at the bottom of the Reviews class, make sure you set up your database as explained in Chapter 12.

6. If you're using a non-English version of VS you need to pluralize the names of the entity sets and properties yourself. To do this, click the Genre class in the Designer, open its Properties Grid by pressing F4, and change the Entity Set Name from Genre to Genres. Repeat this for theReview class and change its Entity Set Name to Reviews. Finally, click the Review property on the diagram for the Genre class (located under the Navigation Properties header in Figure 14.2), press F2 to rename the item, and enter Reviews as the new name. Because a Review only belongs to a single Genre, you don't need to pluralize the Genre property of the Review class.

7. Click somewhere on an empty spot of the designer surface and press F4 to open the Properties Grid. Change the Code Generation Strategy property from None to Default.

8. Save and close the diagram and then delete the files PlanetWrox.tt and PlanetWrox.Context.tt from the App_Code folder.

9. Open All.aspx from the Reviews folder, switch it into Design View, and drag a GridView from the Toolbox onto the page. If you don't have this page, create it now and base it on your custom template.

10. Double-click the page in the gray, read-only area to have VS set up a handler for the Page's Load event and add the following code. Be sure to read the next paragraph after the code example to learn how to solve an error you may get when typing in this code.

VB.NET

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
  Using myEntities As New PlanetWroxEntities()
    Dim authorizedReviews = From review In myEntities.Reviews
                            Where review.Authorized = True
                            Order By review.CreateDateTime Descending
                            Select review
    GridView1.DataSource = authorizedReviews
    GridView1.DataBind()
  End Using
End Sub
 

C#

protected void Page_Load(object sender, EventArgs e)
{
  using (PlanetWroxEntities myEntities = new PlanetWroxEntities())
  {
    var authorizedReviews = from review in myEntities.Reviews
                            where review.Authorized == true
                            orderby review.CreateDateTime descending
                            select review;
    GridView1.DataSource = authorizedReviews;
    GridView1.DataBind();
  }
}

Notice how you immediately get an error when you type PlanetWroxEntities because it's defined in a namespace that is not in scope. You can fix the problem in two ways. You can type Imports PlanetWroxModel if you're using VB.NET or using PlanetWroxModel; if you're using C# at the top of the code file. Alternatively, you can click the word PlanetWroxEntities once and then press Ctrl+. (Ctrl+Dot) to bring up a dialog box that lets you choose the fix for the problem, shown in Figure 14.3 for the C# language. Choose the first item and VS adds the necessary Imports/using statement for you. In C# you can also right-click the word PlanetWroxEntities and then click the Resolve menu to bring up a similar menu.

11. Save all your changes and press Ctrl+F5 to open the page. You'll get a screen full of reviews that have been retrieved from the Review table in the database, as shown in Figure 14.4.

The page looks rather messy because of the way the data is presented in the GridView, but in later exercises you see how to improve the layout of the grid and the data.

Figure 14.1

14.1

Figure 14.2

14.2

Figure 14.3

14.3

Figure 14.4

14.4

How It Works

EF comes with an object-relational designer (accessible in VS) that enables you to create an object model that is accessible through code based on the tables in your database. By adding tables to this designer, VS generates code for you that enables you to access the underlying data in the database without writing a lot of code. The classes that are added to the designer are stored in the .edmx file and its Code Behind files. The designer file (the Code Behind of the PlanetWrox.edmx file) contains a class that inherits from ObjectContext, the main object in EF that provides access to your database. In the preceding exercise, this class is called PlanetWroxEntities (named after the .edmx file) and you use it to access the data in the tables you added to the diagram. Although you normally don't need to look at the generated code, you can open the filePlanetWrox.designer.vb or PlanetWrox.designer.cs file and see what code has been generated for you. The *.tt files you deleted enable you to customize the code that is generated based on the underlying data model. However, in most cases you don't have to do this, and you can rely on the default code generation instead. That's why you deleted these files, and configured the EF model to use its default code generation strategy.

The designer is smart enough to detect the relationships in the database and is thus able to create the necessary relationships in code as well, as you saw in Figure 14.2. The model defines two main object types, Review and Genre, both of which also have collection counterparts calledReviews and Genres, respectively. These collections are referred to as entity sets. Note that on English versions of VS the designer has correctly pluralized the names of the Review and Genre tables (Reviews and Genres, respectively), making it easier to see what is a collection (Reviews) and what is a single instance of an object (Review). For other language versions of VS, you had to apply this logic yourself using the Entity Designer.

After the model has been generated, you can execute LINQ queries against it to get data out of the underlying database. To access the data, you need an instance of the ObjectContext class, which is created inside the Using block in the code. A Using block (using in C#) is used to wrap code that creates a variable that must be disposed of (cleared from memory) as soon as you're done with it. Because the myEntities variable holds a (scarce) connection to the SQL Server database, it's a good idea to wrap the code that uses it in a Using block, so the object is destroyed at the end of the block and the connection is released. This myEntities object then exposes your data (such as reviews and genres) that you can use in a query:

VB.NET

Using myEntities As New PlanetWroxEntities()
  Dim authorizedReviews = From review In myEntities.Reviews 
                          Where review.Authorized = True 
                          Order By review.CreateDateTime Descending 
                          Select review
  ...
End Using
 

C#

using (PlanetWroxEntities myEntities = new PlanetWroxEntities())
{
  var authorizedReviews = from review in myEntities.Reviews
                          where review.Authorized == true
                          orderby review.CreateDateTime descending
                          select review;
  ...
}

Note that this query looks similar to the SQL that you learned in the previous chapters. Under the hood, the run time converts this LINQ query into its SQL counterpart and executes it against the underlying database. Within this query, the variable review in the From clause is used to refer to the review in the other parts of the query (Where, Order By, and Select), enabling you to specify the select, filter, and ordering criteria.

What's important to realize is that EF uses a mechanism called lazy loading, which means sub objects are not loaded until you explicitly tell them to. What this means is that in the previous example the Genre properties of the Review objects you've queried are null in C# and Nothing in VB.NET and don't contain any data. As soon as your code tries to access them, they are loaded by executing another query to the database. This can greatly improve performance if you don't need these sub objects. However, if you're sure you need them in your code beforehand, executing a separate SQL statement for each item results in a lot of overhead. In that case, you can preload the objects with the initial query. To express that you want to include these objects as well, you use the Include method for the types you want to query:

VB.NET

Dim authorizedReviews = From review In myEntities.Reviews.Include("Genre") 
                        Where review.Authorized = True 
...
 

C#

var authorizedReviews = from review in myEntities.Reviews.Include("Genre")
                        where review.Authorized == true
...

With this addition to the query, the Review objects now have their Genre property correctly filled with data. Though this may seem a little counterintuitive and counterproductive at first, it's actually quite a nice feature. If you don't need the extra Genre property in a specific page, you don't take the performance hit of selecting and returning these objects. If you do need them, all you need to add is a single call to Include.

Besides the Reviews collection the model also contains a Genres collection. When you want to select all the genres in the database, you can use this query:

VB.NET

Dim allGenres = From genre In myEntities.Genres 
                Order By genre.Name 
                Select genre
 

C#

var allGenres = from genre in myEntities.Genres
                orderby genre.Name
                select genre;

In addition to these two separate objects and their collections, both objects have properties that refer to each other's type. For example, a Review instance has a Genre property that provides additional information about the genre to which the review was assigned. A Genre instance in turn has a Reviews collection property, giving you access to all reviews posted in that genre. You see later how to make use of these properties.

From the keywords used in the first query in this Try It Out, it's probably easy to see what the query does: It gets a list of all the reviews in the system that have been authorized and orders them in descending order on their creation date. The result of the query is then assigned to theauthorizedReviews variable. Notice that in both languages you can spread out the query over multiple lines to improve readability. This is not required, but you're encouraged to do it anyway because it makes your queries a lot easier to understand and maintain.

You may notice some strange syntax in the query. The VB.NET example doesn't use an As clause to define the type of the variable. Similarly, the C# snippet uses the var keyword, also without a type name. Although you may not conclude it from these code snippets, in both languages the variable authorizedReviews is still strongly typed and not just a variable with an undefined type.

Note

Strongly typed refers to the fact that the variable's type is explicitly defined when it's declared. Once you've defined the type for a variable (using Dim in VB or the type's name in C#) you cannot change it anymore at run time. Strongly typed languages—such as C# and VB.NET—bring many advantages, including the ability to check the types being used at compile time, something that a weakly typed programming language cannot do.

Because the code didn't state the type for authorizedReviews (the example used Dim or var instead), .NET needs a different solution to determine the type. This is done by a concept called type inference, where the compiler is able to infer the type for a variable by looking at the right side of the assignment. In this case, the compiler sees that a list of Review objects will be returned from the query, and correctly types the authorizedReviews variable as a generics type IQueryable(Of Review) in VB.NET syntax or IQueryable<Review> in C#. Although this looks a little scary and incomprehensible at first, it becomes much easier to understand if you simply read it as “a bunch of Review objects that you can access in queries.” In most cases you can also explicitly specify the return type of the variable instead of using var or a Dim statement without a data type, but exceptions do exist, as you'll see later when anonymous objects are discussed.

These Review objects are then assigned to the DataSource property of the GridView. In previous chapters you saw how to use the DataSourceID property to connect a control such as the GridView to a data source control like the SqlDataSource. By using the DataSource property instead, you can assign the actual data yourself, which the control then uses to build up the UI:

VB.NET

GridView1.DataSource = authorizedReviews
GridView1.DataBind()
 

C#

GridView1.DataSource = authorizedReviews;
GridView1.DataBind();

By calling DataBind() on the GridView you instruct the control to display the individual Review objects on the page. Because the GridView control's AutoGenerateColumns property is True by default, the control creates a column for each property it finds on the Review object. Later you see how to customize the control and the data that is being assigned to the DataSource property.

In the following section you learn more about the LINQ query syntax, the language that drives the querying capabilities of .NET.

Introducing Query Syntax

The query you saw in the previous example is quite simple; it requests all the authorized reviews from the system and returns them in a sorted order. However, the querying capabilities of LINQ are much more powerful than this. In this section you learn more about the LINQ query syntax that you use to query your object model. Remember, LINQ syntax is not invented just for the Entity Framework. Most of the LINQ concepts that follow can also be used in the other LINQ implementations, such as LINQ to Objects and LINQ to ADO.NET.

Standard Query Operators

LINQ supports a large number of query operators — keywords that enable you to select, order, or filter data that is to be returned from the query. Although all of the examples in this chapter are discussed in the context of EF, you can easily apply them to the other LINQ implementations as well. In the following section you get an overview of the most important standard query operators, each followed by an example. Each of the examples uses the object model and the ObjectContext object called myEntities you created earlier as the data source to query against.

Select

The Select keyword (select in C#) is used to retrieve objects from the source you are querying. In this example you see how to select an object of an existing type. Later in this chapter you see how to define new object shapes on the fly.

VB.NET

Dim allReviews = From r In myEntities.Reviews
                 Select r
 

C#

var allReviews = from r in myEntities.Reviews
                 select r;

The r variable in this example is referred to as a range variable that is only available within the current query. You typically introduce the range variable in the From clause, and then use it again in the Where and Select clauses to filter the data, and to indicate the data you want to select. Although you can choose any name you like, you often see single-letter variables like the r (for Review) or you see the singular form of the collection you are querying (review instead of r in the preceding examples).

From

Although not considered a standard query operator — because it doesn't operate on the data but rather points to the data — the From clause (from in C#) is an important element in a LINQ query, because it defines the collection or data source that the query must act upon. In the previous example, the From clause indicates that the query must be executed against the Reviews collection that is exposed by the myEntities object in EF.

Order By

With Order By (orderby in C#, without the space that VB.NET uses) you can sort the items in the result collection. Order By is followed by an optional Ascending or Descending (ascending and descending in C#) keyword to specify sort order. You can specify multiple criteria by separating them with a comma. The following query returns a list of genres, first sorted by SortOrder in descending order, then sorted on their Name in ascending order (the default):

VB.NET

Dim allGenres = From g In myEntities.Genres 
                Order By g.SortOrder Descending, g.Name 
                Select g
 

C#

var allGenres = from g in myEntities.Genres
                orderby g.SortOrder descending, g.Name
                select g;

Where

Just like the WHERE clause in SQL, the Where clause in LINQ (where in C#) enables you to filter the objects returned by the query. The following query returns all authorized reviews:

VB.NET

Dim authorizedReviews = From r In myEntities.Reviews 
                        Where r.Authorized = True 
                        Select r
 

C#

var authorizedReviews = from r in myEntities.Reviews
                        where r.Authorized == true
                        select r;

Note that the Where clause uses the language's standard equality operator: a single equals sign (=) in VB.NET and two of them in C#.

Sum, Min, Max, Average, and Count

These aggregation operators enable you to perform mathematical calculations on the objects in the result set. For example, to retrieve the number of reviews, you can execute this query:

VB.NET

Dim numberOfReviews = (From r In myEntities.Reviews
                      Select r).Count()
 

C#

var numberOfReviews = (from r in myEntities.Reviews
                      select r).Count();

Note that the Count method is applied to the entire result set. Therefore, you need to wrap the entire statement in parentheses followed by a call to Count. Without the parentheses you'll get an error. The numberOfReviews variable in this example will be inferred as an integer and contains the number of items in the Review table.

Take, Skip, TakeWhile, and SkipWhile

Take and Skip enable you to make sub-selections within the result set. This is ideal for paging scenarios where only the items for the current page are retrieved. Take gets the requested number of elements from the result set and then ignores the rest, whereas Skip ignores the requested number of elements and then returns the rest.

Within EF, the Take and Skip operators are translated to SQL statements as well. This means that paging takes place at the database level, and not in the ASP.NET page. This greatly enhances performance of the query, especially with large result sets, because not all elements have to be transferred from the database to the ASP.NET page.

For Skip to work in LINQ to Entities, you must add an Order By clause (orderby in C#) to your query to sort the results before the designated number of rows are skipped. Databases may return results in an unpredictable order if you don't add an explicit ORDER BY statement, so adding the Order By action in your LINQ to Entities query is needed to get a consistent result from the Skip method because rows are sorted first before they are skipped and taken.

The following example shows you how to retrieve the second page of rows, given a page size of 10:

VB.NET

Dim someReviews = (From r In myEntities.Reviews
                  Order By r.Title
                  Select r).Skip(10).Take(10)
 

C#

var someReviews = (from r in myEntities.Reviews
                  orderby r.Title
                  select r).Skip(10).Take(10);

Just as with the Count example, the query is wrapped in a pair of parentheses, followed by the calls to Skip and Take to get the requested rows.

The TakeWhile and SkipWhile query operators work in a similar fashion, but enable you to take or skip items while a specific condition is true. Unfortunately, they don't work in EF, but you can usually work around that by adding a simple Where clause to your query.

Single and SingleOrDefault

The Single and SingleOrDefault operators enable you to return a single object as a strongly typed instance. This is useful if you know your query returns exactly one row; for example, when you retrieve it by its unique ID. The following example retrieves the review with an ID of 37 from the database:

VB.NET

Dim review37 = (From r In myEntities.Reviews 
                Where r.Id = 37 
                Select r).Single()
 

C#

var review37 = (from r in myEntities.Reviews
                where r.Id == 37
                select r).Single();

The Single operator raises an exception when the requested item is not found or if the query returns more than one instance. If you want the method to return null (Nothing in VB.NET) — for example, for a Review or Genre that is not found — or the default value for the relevant data type (such as a 0 for an Integer, False for a Boolean, and so on) instead, use SingleOrDefault.

Even though there is only one Review with an Id of 37 in the database, you will still get a collection of reviews (holding only one element) if you omit the call to Single. By using Single you force the result set into a single instance of the type you are querying.

First, FirstOrDefault, Last, and LastOrDefault

These operators enable you to return the first or the last element in a specific sequence of objects. Just as with the Single method, First and Last throw an error when the collection is empty, whereas the other two operators return the default value for the relevant data types.

In contrast to Single, the First, FirstOrDefault, Last, and LastOrDefault operators don't throw an exception when the query returns more than one item. They simply return the first item in the result set.

The Last and LastOrDefault queries are not supported in EF. However, you can easily accomplish the same behavior with First and a descending sort order. The following code snippet shows how to retrieve the oldest (the one with the lowest ID) and the most recent review from the database:

VB.NET

Dim firstReview = (From r In myEntities.Reviews 
                  Order By r.Id 
                  Select r).First()
 
Dim lastReview = (From r In myEntities.Reviews 
                 Order By r.Id Descending
                 Select r).First()
 

C#

var firstReview = (from r in myEntities.Reviews
                  orderby r.Id
                  select r).First();
 
var lastReview = (from r in myEntities.Reviews
                 orderby r.Id descending
                 select r).First();

Simply by reordering the result set in reverse order before executing First, you actually get the last row in the sequence. Note that in both cases, the type returned by the query is a true Review object, enabling you to access its properties, such as Id and Title, directly.

Besides this LINQ Query Syntax, LINQ also supports method syntax. For the differences and an example, check out this MSDN article: http://tinyurl.com/MethodVersusQuery.

Shaping Data with Anonymous Types

So far, the queries you have seen in the previous sections returned full types. That is, the queries returned a list of Review instances (such as the Select method), a single instance of Review (Single, First, or Last), or a numeric value (such as Count and Average).

Quite often, however, you don't need all the information from these objects. Figure 14.4 shows a GridView with all the properties from the Review object. To improve the presentation of this list, you usually want to skip properties like Body and Authorized, and instead of the genre ID you probably want to display the genre name. Although you could tell the GridView to display only the columns you want to see, it would be more efficient if you were able to limit the actual data. This is pretty easy to do with anonymous types, another language feature available in C# and VB.NET. An anonymous type is a type whose name and members you don't define up front as you do with other types. Instead, you construct the anonymous type by selecting data and then letting the compiler infer the type for you. The anonymous type can only be accessed within the method that declared it, and as such you cannot return an anonymous type from a method.

If you don't define the actual type and give it a name, how can you access the type and its properties? This is once again done with type inference, where the compiler can see what data is assigned to a variable and then creates a new, anonymous type on the fly.

Creating an anonymous type is easy; instead of selecting the actual object using something like Select review, you use the new keyword in C# and New With in Visual Basic, and then define the properties you want to select between a pair of curly braces:

VB.NET

Dim authorizedReviews = From myReview In myEntities.Reviews 
                 Where myReview.Authorized = True 
                 Select New With {myReview.Id, myReview.Title, myReview.Genre.Name}
 

C#

var authorizedReviews = from review in myEntities.Reviews
                        where review.Authorized == true
                        select new { review.Id, review.Title, review.Genre.Name };

Although the type is anonymous and cannot be accessed by name directly, the compiler is still able to infer the type, giving you full IntelliSense for the new properties that were selected in the query. Figure 14.5 shows how you access the properties of the anonymous type in theauthorizedReviews variable, using the var keyword in C#.

Figure 14.5

14.5

Note that the preceding query accessed the actual Genre property of the Review. Besides its GenreId (defined as a column in the table Review in the database), the Review class also has a strongly typed Genre property, giving you direct access to the genre's properties, like the Name, as the previous query demonstrates.

Besides directly selecting existing properties — as shown in the query that selected the Id and Title of the Review and the Name of the Genre — you can also make up property values and give them different names as you go. For example, the following query creates a new anonymous type that renames the Id as Number, limits the Title to the first 20 characters, and contains a boolean value that determines whether the item has been updated in the database previously:

VB.NET

Dim allReviews = From myReview In myEntities.Reviews
                 Select New With 
                 {
                     .Number = myReview.Id,
                     .Title = myReview.Title.Substring(0, 20),
                     myReview.Genre.Name,
                     .HasBeenUpdated = (myReview.UpdateDateTime > 
                           myReview.CreateDateTime)
                 }
 

C#

var allReviews = from myReview in myEntities.Reviews
                 select new 
                 {
                   Number = myReview.Id, 
                   Title = myReview.Title.Substring(0, 20), 
                   myReview.Genre.Name, 
                   HasBeenUpdated = (myReview.UpdateDateTime > 
                          myReview.CreateDateTime)
                 };

Note the difference between VB.NET and C#; in the VB.NET example, the names of the new properties (Number, Title, and HasBeenUpdated) are prefixed with a period (.). C# doesn't have this requirement and lets you write new property names directly. If you don't introduce a new name (as is the case with the genre name in the preceding example), the name of the property you're selecting is used. This means that the genre name is stored in a property called Name.

The ability to select extra properties that are not present in the original object gives you great flexibility in the data you display. This example determines whether the current review has been updated by comparing the CreateDateTime and UpdateDateTime properties. The result of this comparison (a boolean with the value True or False) is then stored in the property HasBeenUpdated. You can select nearly anything you want, including the current date and time, complex calculations, substrings or combinations of properties, and so on.

In the following exercise you see how to create a new anonymous type that has a Reviews collection as a property. You use this type to create a list of all the available genres in the database, and the reviews that each genre contains.

Try It Out: Working with Queries and Anonymous Types

In this Try It Out you create a page that lists all the available genres, each followed by the list of reviews that have been published in that genre. You use a Repeater control to display the list of genres and a nested BulletedList to display the inner reviews. When you're done, you should see a list similar to the one displayed in Figure 14.6.

1. Open the AllByGenre.aspx page from the Reviews folder. Make sure the page is in Markup View and then drag a Repeater from the Data category of the Toolbox between the opening and closing tags of the cpMainContent content placeholder.

2. Inside the Repeater create an <ItemTemplate> element that in turn contains an <h3> element that contains a Literal. You should end up with this code:

<asp:Repeater ID="Repeater1" runat="server">
  <ItemTemplate>
    <h3><asp:Literal ID="Literal1" runat="server"></asp:Literal></h3>
  </ItemTemplate>
</asp:Repeater>

3. Set the Text property of the Literal control to <%# Eval(”Name”) %>. Instead of double quotes, make sure you use single quotes to delimit the property's value. You need this or otherwise the double quotes surrounding Name would prematurely close off the Text property.

<asp:Literal ID=”Literal1" runat=”server” 
            Text='<%# Eval(”Name”) %>'></asp:Literal>

4. Below the <h3> element, drag and drop a BulletedList control from the Standard category and set the following properties on the control. You can either enter them directly in Markup View or use the Properties Grid.

Property Name

Value

ID

ReviewList

DataSource

<%# Eval(”Reviews”)%> (make sure you use single quotes again to wrap this attribute value, as shown in the following code snippet)

DataTextField

Title

DisplayMode

Text

You should end up with the following control code:

<asp:BulletedList ID=”ReviewList” runat=”server” 
    DataSource='<%# Eval(”Reviews”)%>’ DataTextField=”Title” 
    DisplayMode=”Text”></asp:BulletedList>

5. Switch to Design View and double-click the page somewhere in the read-only area defined by the master page to set up a handler for the Load event of the page. Within the handler, write the following code. Again, use Ctrl+. (Ctrl + Dot) to let VS insert the right namespace for thePlanetWroxEntities class.

VB.NET

Imports PlanetWroxModel
... ‘ Class definition goes here
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
  Using myEntities As New PlanetWroxEntities()
    Dim allGenres = From genre In myEntities.Genres.Include(”Reviews”)
                    Order By genre.Name
                    Select New With {genre.Name, genre.Reviews}
    Repeater1.DataSource = allGenres
    Repeater1.DataBind()
  End Using
End Sub
 

C#

using PlanetWroxModel;
... // Class definition goes here 
protected void Page_Load(object sender, EventArgs e)
{
  using (PlanetWroxEntities myEntities = new PlanetWroxEntities())
  {
    var allGenres = from genre in myEntities.Genres.Include(”Reviews”)
                    orderby genre.Name
                    select new { genre.Name, genre.Reviews };
    Repeater1.DataSource = allGenres;
    Repeater1.DataBind();
  }
}

6. Save the changes to your page and then request it in the browser. You should see a result similar to that shown in Figure 14.6, where each genre appears as a group header above the lists with reviews.

How It Works

You have two important things to look at in this exercise. First of all, there's the LINQ query that is used to get the genres and reviews from the database. This query (that uses Include(”Reviews”) to prevent lazy loading as you saw earlier) creates a new anonymous type with two properties: the Name of the Genre as a String and a collection of Review objects called Reviews. The class diagram for the new anonymous type could look like Figure 14.7.

These Name and Reviews properties are then used in the second important part: the Repeater control with the nested bulleted list. First, take a look at the Repeater:

<asp:Repeater ID=”Repeater1” runat=”server”>
  <ItemTemplate>
    <h3><asp:Literal ID=”Literal1” runat=”server” 
               Text='<%# Eval(”Name”) %>'></asp:Literal></h3>
    <!-- BulletedList here -->
  </ItemTemplate>
</asp:Repeater>

Although you haven't worked with the Repeater control before, it may look familiar, because it works in a manner similar to the other data controls. Within the <ItemTemplate> you define the markup that you want repeated for each item in the data source. Using Eval you can get the value of the genre's Name property and assign it to the Literal, which is wrapped in a pair of <h3> tags. A similar construct is used for the BulletedList to feed it a DataSource:

<asp:BulletedList ID=”BulletedList1” runat=”server” DisplayMode=”Text”
        DataSource='<%# Eval(”Reviews”)%>’ DataTextField=”Title” />

In addition to assigning simple properties like the Text of the Literal from the Name of the underlying data item, you can also use Eval to get complex properties. In this example, Eval(”Reviews”) is used to get the collection of Reviews for the current Genre. The BulletedList control then understands how to handle this data source and retrieves the Title from each individual Review object and then displays that in the list. The diagram in Figure 14.8 shows you how each Genre contains one or more reviews, whose titles are displayed below the name of the genre.

Figure 14.6

14.6

Figure 14.7

14.7

Figure 14.8

14.8

After you have set up the Repeater and defined the query, you need to start the data-binding process. You do this by assigning the results of the query to the DataSource property of the Repeater, followed by a call to DataBind() as shown in this C# example:

    Repeater1.DataSource = allGenres;
    Repeater1.DataBind();

These two lines set things in motion: as soon as you call DataBind(), the query is executed and the relevant genres and reviews are retrieved from the database. In this example, the genres are sorted on their Name, but obviously you can order on other properties, such as SortOrder, as well. The Repeater then loops through each item in the result set (this item is the anonymous type you just saw) and uses that item's Name to fill in the <h3> element with the genre name. The Repeater then assigns the Reviews collection to the inner BulletedList control's DataSource property. This control loops over the available Review instances, using their Title to build up the bulleted list. In this example, you see that genres without reviews are displayed in the list as well. In the “Exercises” section at the end of the chapter, you'll find an exercise that shows you how to hide empty genres.

Although it may take you some time to fully understand the principles behind these LINQ queries and the Entity Framework, I am sure you are beginning to appreciate their power and accessibility. With just a few lines of code and a few controls, you can create powerful data presentation pages.

However, it's possible to create LINQ queries that execute against EF and use them with the ASP .NET Server Controls with even fewer lines of code. You see how this works in the next section, which deals with the EntityDataSource, the ListView, and the DataPager controls.

Using Server Controls with LINQ Queries

So far you have seen one way to bind the results of a LINQ query against EF to a control in your ASPX page: assign the data to the control's DataSource property and then call DataBind. This way of getting data into the controls has a few shortcomings. First of all, this method does not support the editing, updating, and deleting of data directly. Secondly, because you define the data source in the Code Behind, the GridView doesn't know what data you're feeding it until run time, so you get no tool support to set up its columns. These shortcomings are easy to overcome by using the server controls, including the ListView and the EntityDataSource control.

Using Data Controls with the Entity Framework

In the previous chapter you were introduced to some of the data controls, like the GridView and the SqlDataSource. But ASP.NET 4.5 ships with more controls that let you create data-driven pages with very few lines of code. Two of these controls provide a visual interface in your ASP.NET pages, and the third one works as a bridge between your data-bound controls and your underlying data sources. The following table briefly introduces you to these controls.

Control

Description

EntityDataSource

As with the SqlDataSource that you saw in previous chapters, the EntityDataSource works as a bridge between your data-bound controls and the underlying data source: EF in this case.

ListView

The ListView control provides a template-driven visual interface that enables you to display, insert, edit, and delete items in a database, providing full CRUD services.

DataPager

The DataPager is used together with the ListView and enables you to paginate the data in the data source, feeding data to users in bite-sized chunks instead of all rows at once.

The next few sections provide you with more detail about these controls and show you how to use them in a few Try It Out exercises.

Introducing the EntityDataSource Control

As its name implies, the EntityDataSource is a close relative of the SqlDataSource and other data source controls. The EntityDataSource control is to EF what the SqlDataSource control is to SQL-based data sources: It provides a declarative way to access your model. Just like the SqlDataSourcecontrol, EntityDataSource gives you easy access to the CRUD operations and additionally makes sorting and filtering of data very easy. The following table describes the main properties and capabilities of this control.

Property

Description

EnableDelete
EnableInsert
EnableUpdate

Determine whether the control provides automatic insert, update, and delete capabilities. When enabled, you can use the control together with data-bound controls like the GridView or ListView to support data management.

ContextTypeName

The name of the ObjectContext class that the control should use. In the examples in this book, this type name is PlanetWroxEntities.

EntitySetName

The name of the entity set from the EF model you want to use, such as Reviews.

Select
OrderBy
Where

Enable you to define the query that the EntityDataSource control fires against the model. Each of these properties maps to one of the query operators you've seen before.

Together with a data-bound control, the EntityDataSource provides you full access to the underlying SQL Server database through LINQ to Entities. The next exercise shows you how to use the control in your ASPX pages.

Try It Out: A Simple EntityDataSource Application

In this Try It Out you start building the Gig Pics feature of Planet Wrox, a section of the website where users can upload photos they created during concerts of their favorite bands. You see how to let a user create a new photo album that acts as a container for the pictures that are uploaded. You see how to use the EntityDataSource and a DetailsView to create a user interface that enables users to enter a name for a new photo album into the system. In later exercises you see how to add pictures to this photo album.

1. Add the following two tables to your database using SQL Server Management Studio. Refer to Chapter 12 for more details about creating tables, primary keys, and identity columns.

Table 14.4 Picture

Column Name

Data Type

Description

Id

int

The unique ID (identity and primary key) of the picture

Description

nvarchar(300)

A description of the picture

ToolTip

nvarchar(50)

A tooltip displayed when you hover over a picture

ImageUrl

nvarchar(200)

The virtual path to the picture on disk

PhotoAlbumId

int

The ID of the photo album this picture belongs to

For both tables, make sure that none of the columns in the two tables are nullable by unchecking their Allow Nulls check boxes. Make the Id column the primary key by clicking it once, and then clicking the yellow key icon on the Table Designer toolbar. Additionally, make this column the table's Identity column by setting the (Is Identity) property on the Column Properties Grid to Yes. Refer to Chapter 12 if you're not sure how to do this. Finally, make sure you have the casing of the table and column names right. Later code in this book assumes you wrote the table and column names as shown here.

2. On the Object Explorer, open the database diagram that you created in Chapter 12. Right-click the diagram and choose Add Table. Select the two new tables, click Add, and then click Close. Arrange the two new tables side by side if necessary. Next, drag the Id column from thePhotoAlbum table onto the PhotoAlbumId column of the Picture table. Confirm that the Primary Key Table is PhotoAlbum with Id as the selected column and that Picture is the Foreign Key Table with PhotoAlbumId as the selected column, as shown in Figure 14.9.

3. Click OK twice to apply the changes and then save and close the diagram. Click Yes to confirm the changes made to the two tables.

4. Next, switch back to VS, and open the ADO.NET Entity Framework Model file PlanetWrox.edmx from the App_Code folder by double-clicking it. Right-click an empty spot of the diagram and choose Update Model from Database. In the wizard that appears, expand Tables, then dbo, and then check the two tables you just created: PhotoAlbum and Picture. Click Finish to have the two tables added to your model. Your diagram should end up like Figure 14.10. Note that I reorganized the diagram by dragging the tables side by side to make it easier to see them.

If you're using a non-English version of VS, you need to pluralize the names of the entity sets and properties again. To do this, click the Picture class, open its Properties Grid by pressing F4, and change the Entity Set Name from Picture to Pictures. Repeat this for the PhotoAlbum class and change its Entity Set Name to PhotoAlbums. Finally, click the Picture property on the diagram for the PhotoAlbum class, press F2 to rename the item, and then enter Pictures as the new name.

Save all your changes and close the diagram.

5. Create a new Web Form based on your custom template in the root of the site and call it NewPhotoAlbum.aspx. Give the page a title of Create New Photo Album.

6. Switch the page into Design View and from the Data category of the Toolbox, drag a DetailsView control and drop it into the cpMainContent placeholder. On the DetailsView control's Smart Tasks panel, open the Choose Data Source drop-down list and select <New data source>. In the Data Source Configuration Wizard dialog box, click the Entity icon and click OK. In the Named Connection drop-down, choose PlanetWroxEntities.

Note

If you get an error about incorrect metadata, close the dialog box, delete the existing EntityDataSource control from the page, and manually drag a new one from the Toolbox. On the DetailsView control's Smart Tasks panel, choose the new data source control. Then open the EntityDataSource control's Smart Tasks panel and choose Configure Data Source.

Click Next to go to the Configure Data Selection screen, shown in Figure 14.11. From the EntitySetName drop-down list, choose PhotoAlbums.

7. For this exercise, you need insert behavior, so check off the Enable Automatic Inserts check box. Click Finish to close the Configure Data Source wizard.

8. If you don't see the Id and Name columns in the DetailsView, but a general Databound Col0 instead, click the Refresh Schema link on the control's Smart Tasks panel.

Enable inserting for the DetailsView control by checking the Enable Inserting item on the same Smart Tasks panel.

9. Open the DetailsView control's Properties Grid and change the DefaultMode from ReadOnly to Insert.

10. Switch to Markup View, locate the BoundField for the Id property of the PhotoAlbum, and set its InsertVisible property to false so you don't get a text box for the ID when inserting a new photo album.

<asp:BoundField DataField=”Id” HeaderText=”Id” ReadOnly=”True”
          SortExpression=”Id” InsertVisible=”false” />

11. Select the EntityDataSource control in Design View, open its Properties Grid, and switch to the Events tab. Double-click the Inserted event, visible in Figure 14.12.

12. At the top of the code file, add the following Imports/using statement to bring your entities model in scope so you can access classes such as PhotoAlbum:

VB.NET

Imports PlanetWroxModel 
 

C#

using PlanetWroxModel;

Then, in the event handler that VS added for you, write the following code that redirects the user to a new page once the photo album has been inserted in the database:

VB.NET

Protected Sub EntityDataSource1_Inserted(sender As Object, 
     e As EntityDataSourceStatusEventArgs) Handles EntityDataSource1.Inserted
  If (e.Entity IsNot Nothing) Then
    Dim myPhotoAlbum As PhotoAlbum = CType(e.Entity, PhotoAlbum)
    Response.Redirect(String.Format(”ManagePhotoAlbum.aspx?PhotoAlbumId={0}”,
                   myPhotoAlbum.Id.ToString()))
  End If
End Sub
 

C#

protected void EntityDataSource1_Inserted(object sender, 
       EntityDataSourceChangedEventArgs e)
{
  if (e.Entity != null)
  {
    PhotoAlbum myPhotoAlbum = (PhotoAlbum)e.Entity;
    Response.Redirect(string.Format(”ManagePhotoAlbum.aspx?PhotoAlbumId={0}”,
                   myPhotoAlbum.Id.ToString()));
  }
}

13. Save all changes and then request NewPhotoAlbum.aspx in the browser.

Figure 14.9

14.9

Figure 14.10

14.10

Figure 14.11

14.11

Figure 14.12

14.12

COMMON MISTAKES

If the page you get is empty, make sure you set the DefaultMode property of the DetailsView to Insert.

Enter a new name for the photo album, such as Foo Fighters playing live at Lowlands 2012, and click the Insert link. You'll get a Resource Not Found error (because you haven't created ManagePhotoAlbum.aspx yet), but you can at least see the ID of the new photo album in the address bar of your browser:

http://localhost:9797/ManagePhotoAlbum.aspx?PhotoAlbumId=1

How It Works

You started this exercise by adding the Picture and PhotoAlbum tables to both the database and the EF diagram. These tables are used to store data about photo albums and the pictures they contain. Each individual picture belongs to a PhotoAlbum referred to by its PhotoAlbumId that points to the Id column of the PhotoAlbum table in the database. The Picture table is designed to only hold data about the picture; the actual picture file is stored on disk, as you see later.

To enable users to create a new photo album, you added a DetailsView control to the page. To make sure the control can be used to insert new photo albums, you enabled inserting and then set the DefaultMode to Insert. This forces the control to jump into insert mode, instead of the default read-only mode. You then hooked up an EntityDataSource to the DetailsView, which takes care of inserting the photo album in the PhotoAlbum table. The code for the EntityDataSource control looks like this:

<asp:EntityDataSource ID=”EntityDataSource1” runat=”server” 
    ConnectionString=”name=PlanetWroxEntities” EnableFlattening=”False”
    DefaultContainerName=”PlanetWroxEntities” EnableInsert=”True” 
    EntitySetName=”PhotoAlbums” OnInserted=”EntityDataSource1_Inserted”>
</asp:EntityDataSource>

The ConnectionString attribute points to a named connection string in Web.config that is used to access the database. The EnableFlattening attribute is used in advanced scenarios when using a version of EF before .NET 4. For .NET 4 and 4.5 you should leave it set to False. If you are using Visual Basic.NET, your code won't have the OnInserted attribute set.

Note how straightforward the EntityDataSource is in this scenario: You point it to a DefaultContainerName, the PlanetWroxEntities in this example, which is the main entrance for the control to get its data. You also turned on inserting by setting EnableInsert to True. Additionally, you set the EntitySetName, so the control knows what object to use from the EF diagram. For simple inserts, this is all you need to do. When the page loads in the browser, the DetailsView renders a user interface that enables you to enter a new name for the photo album. When you click Insert, the data you entered is assembled and forwarded to the EntityDataSource. This control in turn creates a new PhotoAlbum instance and then saves it in the database by sending the appropriate INSERT SQL statement to the database.

In many situations, this standard behavior is not enough. You may need to validate the data that is entered or you may have a need to change the actual data before it gets sent to the database. You see an example of the latter in a subsequent Try It Out when you upload images to the server.

Another common requirement is retrieving the ID of the newly created item, which is then sent to the next page. This exercise used the following code to accomplish that:

VB.NET

Dim myPhotoAlbum As PhotoAlbum = CType(e.Entity, PhotoAlbum)
Response.Redirect(String.Format(”ManagePhotoAlbum.aspx?PhotoAlbumId={0}”, 
                  myPhotoAlbum.Id.ToString()))
 

C#

PhotoAlbum myPhotoAlbum = (PhotoAlbum)e.Entity;
Response.Redirect(string.Format(”ManagePhotoAlbum.aspx?PhotoAlbumId={0}”, 
                  myPhotoAlbum.Id.ToString()));

The cool thing about the EntityDataSource control is that it works with strongly typed objects, where the type maps to the tables you added to the model diagram. In this case, you are working with real instances of PhotoAlbum, the class that represents the photo albums in the system. This enables you to retrieve the photo album you have inserted in the database in the Inserted event of the data source control. The e argument exposes a property called Entity that contains a reference to the new photo album. Simply by casting it to a real PhotoAlbum (by using CType in VB.NET or putting the class name in parentheses before it in C#), you can access the properties of the PhotoAlbum, including its new ID that has been generated by the database (through the Identity settings on the ID column) and then stored in the Id property of the PhotoAlbum. The final line in the event handler takes the user to the next page and sends the ID of the new photo album in the query string. This code has been wrapped in an if statement that checks if e.Entity is not null / Nothing. If an error occurs while updating the database, the Entity property will be null, and the cast to a PhotoAlbum will fail. The error you see in the browser will then be about the failed cast, and not about the database error, making it hard to see the root cause of the problem. By using the if check, you cast and redirect only when the database update succeeded, whereas you see the database error in case it failed.

Note that you get an error when you leave the name field empty and click Insert. The previous chapter showed you how to modify the DetailsView to insert validation controls to its templates.

Now that you can insert new photo albums, the next logical step is to add pictures to the photo album. In the next exercise you see how to create a user interface with the ListView control that enables a user to upload new pictures in the photo album.

Introducing the ListView Control

Up until now, you have seen a few data-bound controls at work. You saw the GridView, which is quite powerful because it supports updates, deletes, sorting, and paging of data, but lacks inserting and generates a lot of HTML markup. You also saw the Repeater control that gives you precise control over the generated HTML, but lacks most advanced features that the other data controls have, such as update and delete behavior and sorting and filtering capabilities. And finally, you saw the DetailsView that enables you to insert or update one row at a time.

The ListView is a “best of all worlds” control, combining the rich feature set of the GridView with the control over the markup that the Repeater gives you and adding the insert behavior of the DetailsView. The ListView enables you to display data in a variety of formats, including a grid (rows and columns like the GridView), as a bulleted list (similar to how you set up the Repeater earlier in this chapter), and in Flow format, where all the items are placed in the HTML after each other, leaving it up to you to write some CSS to format the data.

The ListView displays and manages its data through templates that enable you to control many of the different views that the ListView gives you on its underlying data. The following table describes all the available templates that you can add as direct children of the ListView control in the markup of the page.

Template

Description

<LayoutTemplate>

Serves as a container. It enables you to define a location where the individual data items are placed. The data items, presented through the ItemTemplate and AlternatingItemTemplate, are then added as children of this container.

<ItemTemplate>
<AlternatingItemTemplate>

Define the read-only mode for the control. When used together, they enable you to create a “zebra effect,” where odd and even rows have a different appearance (usually a different background color).

<SelectedItemTemplate>

Enables you to define the look and feel of the currently active, or selected, item.

<InsertItemTemplate>
<EditItemTemplate>

These two templates enable you to define the user interface for inserting and updating items in the list. You typically place controls like text boxes, drop-down lists, and other server controls in these templates and bind them to the underlying data source.

<ItemSeparatorTemplate>

Defines the markup that is placed between the items in the list. Useful if you want to add a line, an image, or any other markup between the items.

<EmptyDataTemplate>

Displayed when the control has no data to display. You can add text or other markup and controls to it to tell your users there's no data to display.

<GroupTemplate>
<GroupSeparatorTemplate>
<EmptyItemTemplate>

Used in advanced presentation scenarios where data can be presented in different groups.

Although this long list of templates seems to suggest you need to write a lot of code to work with the ListView, this is not always the case. First of all, VS creates most of the code for you based on the data that is exposed by controls such as the EntityDataSource. Secondly, you don't always need all templates, enabling you to minimize the code for the control.

Besides the numerous templates, the control has the following properties that you typically set to influence its behavior.

Property

Description

ItemPlaceholderID

The ID of a server-side control placed within the LayoutTemplate. The control referred to by this property is replaced by all the repeated data items when the control is displayed on-screen. It can be a true server control like an <asp:PlaceHolder> or a simple HTML element with a valid ID and its runat attribute set to server (for example, <ul runat=”server” id=”MainList”></ul>). If you don't set this property, ASP.NET tries to find a control with an ID of itemPlaceholder and uses that control instead.

DataSourceID

The ID of a data source control on the page, such as an EntityDataSource or a SqlDataSource control.

InsertItemPosition

The enumeration for this property contains three values — None, FirstItem, and LastItem — to determine the position of the InsertItemTemplate: either at the beginning or end of the list, or not visible at all.

Just like the other data-bound controls, the ListView has a number of events that fire at specific moments during the control's lifetime. For example, it has ItemInserting and ItemInserted events that fire right before and after an item has been inserted in the underlying data source. Similarly, it has events that trigger right before and after you update or delete data. You see more about handling these kinds of events in the next chapter.

Besides the templates, properties, and events you just saw, the ListView has more to offer. For a detailed explanation of the ListView control and all of its members and behavior, check out the MSDN documentation at http://bit.ly/dCAooK.

The next exercise shows you how to put all of this information together. You see how to define the various templates and set the relevant properties to control the look and feel of the ListView control.

Try It Out: Inserting and Deleting Data with the ListView Control

Inserting items with the ListView can be just as easy as with the DetailsView: You point the control to a data source and let VS create the necessary templates for you. However, in many real-world websites, these default templates won't cut it. You may want to display fewer fields than are available in the data source, validate data before it gets sent to the database, or store data at a different location than the database. For example, you may want to store uploaded images on disk rather than in the database and then store only a reference to the file in the database table. The next exercise shows you how to customize the ListView templates and handle the Inserting event of the EntityDataSource when you build functionality to add pictures to your photo albums.

This exercise has you work with a lot of code that is generated automatically by VS. Most of what you need to do in this exercise is remove code instead of add new code. If you get lost somewhere, or you feel your code does not look like it should, remember this chapter comes with the full source code that you can download from the Wrox website, so you can compare your code with mine.

1. In the root of the website, create a new Web Form based on your custom template. Call it ManagePhotoAlbum.aspx, set its Title to Manage Photo Album, and switch it into Design View.

2. From the Data category of the Toolbox, drag a ListView control onto the page in the cpMainContent placeholder and then hook it up to an EntityDataSource control by choosing <New data source> in the Choose Data Source drop-down list on the Smart Tasks panel (just as you did with the DetailsView earlier). Click the Entity icon, click OK, choose PlanetWroxEntities as the named connection, and click Next.

In the Configure Data Selection dialog box of the EntityDataSource control's wizard, visible in Figure 14.11, choose Pictures from the EntitySetName drop-down list.

3. Check the first and the last check box of the three at the bottom of the screen to give the EntityDataSource insert and delete support. Finally, click Finish to close the Configure Data Source wizard.

4. Back in Design View, select the EntityDataSource control, open its Properties Grid, locate the Where property, and open its Expression Editor by clicking the ellipsis for that property. You may recall that the ManagePhotoAlbum.aspx page receives the photo album ID through the query string, so you'll need to set up a QueryStringParameter in this step to filter the ListView to those pictures belonging to the designated photo album. To do this, enter it.PhotoAlbum.Id = @photoAlbumId in the Where Expression box at the top of the dialog box. Then click Add Parameter, enter PhotoAlbumId as the name, choose QueryString from the Parameter Source drop-down, and enter PhotoAlbumId as the QueryStringField. Next, click the Show Advanced Properties link and change the Type property of the parameter to Int32. Your dialog box should now look likeFigure 14.13. When you're done, click OK to dismiss the dialog box.

5. Back in the page, the ListView should still appear as a plain rectangle, shown in Figure 14.14, because you haven't provided any template information yet.

6. On the Smart Tasks panel of the ListView, choose Configure ListView. (If you don't see this link, click Refresh Schema first and reopen the Smart Tasks panel.) A dialog box appears that enables you to choose the layout of the control, a style, and whether or not you want to enable operations such as inserting and updating. Choose Bulleted List as the layout, and check the Enable Inserting and Enable Deleting items so your dialog box ends up as shown in Figure 14.15.

7. Click OK to close the dialog box. If you get a dialog box that asks if you want to regenerate the ListView control, click Yes.

8. Switch to Markup View and remove the code for the following templates. To make this as easy as possible, click the relevant opening tag once, then click the tag in the Tag Selector at the bottom of the Document Window to select the entire element and its content, and then press the Delete key. Alternatively, you can collapse the tag using the plus (+) symbol in the left margin, select the whole line, and delete it at once.

· <AlternatingItemTemplate>

· <EditItemTemplate>

· <EmptyDataTemplate>

· <ItemSeparatorTemplate>

· <SelectedItemTemplate>

The only three remaining template elements should now be InsertItemTemplate, ItemTemplate, and LayoutTemplate.

9. Locate the <ul> element in the LayoutTemplate and remove its ID, runat, and style attributes. Then add a class attribute and set it to ItemContainer. You can also remove the empty <div> element that VS added for you below the <ul>. Your <LayoutTemplate> now contains this code:

<LayoutTemplate>
  <ul class=”ItemContainer”>
    <li runat=”server” id=”itemPlaceholder” />
  </ul>
</LayoutTemplate>

10. Locate the ItemTemplate and remove the lines that make up the Id, PhotoAlbumId, and PhotoAlbum columns, bolded in the following code snippet, because you don't need them. Make sure you don't accidentally delete the opening <li> tag:

<li style=””>
  Id:
  <asp:Label ID=”IdLabel” runat=”server” Text='<%# Eval(”Id”) %>’ />
  <br />
  Description:
  <asp:Label ID=”DescriptionLabel” runat=”server” 
             Text='<%# Eval(”Description”) %>’ />
  <br />
  ToolTip:
  <asp:Label ID=”ToolTipLabel” runat=”server” Text='<%# Eval(”ToolTip”) %>’ /><br />
  ImageUrl:
  <asp:Label ID=”ImageUrlLabel” runat=”server” 
             Text='<%# Eval(”ImageUrl”) %>’ /><br />
  PhotoAlbumId:
  <asp:Label ID=”PhotoAlbumIdLabel” runat=”server”
             Text='<%# Eval(”PhotoAlbumId”) %>’ />
  <br />
  PhotoAlbum:
  <asp:Label ID=”PhotoAlbumLabel” runat=”server” Text='<%# Eval(”PhotoAlbum”) %>’ />
  <br />
  <asp:Button ID=”DeleteButton” runat=”server” CommandName=”Delete” Text=”Delete” />
</li>

11. Repeat the previous step for the InsertItemTemplate that is also part of the ListView control's markup.

Compare your code with the following code and make any changes if necessary. Check if your ListView has a DataKeyNames property set to Id and add it if it isn't there. Sometimes VS doesn't add this property, although the code requires it to be there. Also check that the templates contain the correct controls. The order of the templates or the white space can be different in your code.

<asp:ListView ID=”ListView1” runat=”server” DataKeyNames=”Id”
                DataSourceID=”EntityDataSource1” InsertItemPosition=”LastItem”>
  <InsertItemTemplate>
    <li style=””>
      Description:
      <asp:TextBox ID=”DescriptionTextBox” runat=”server” 
                   Text='<%# Bind(”Description”) %>’ /><br />
      ToolTip:
      <asp:TextBox ID=”ToolTipTextBox” runat=”server” 
                   Text='<%# Bind(”ToolTip”) %>’ /><br />
      ImageUrl:
      <asp:TextBox ID=”ImageUrlTextBox” runat=”server” 
                   Text='<%# Bind(”ImageUrl”) %>’ /><br />
      <asp:Button ID=”InsertButton” runat=”server” 
                  CommandName=”Insert” Text=”Insert” />
      <asp:Button ID=”CancelButton” runat=”server” 
                  CommandName=”Cancel” Text=”Clear” />
    </li>
  </InsertItemTemplate>
  <ItemTemplate>
    <li style=””>Description:
      <asp:Label ID=”DescriptionLabel” runat=”server” 
                 Text='<%# Eval(”Description”) %>’ /><br />
      ToolTip:
      <asp:Label ID=”ToolTipLabel” runat=”server” 
                 Text='<%# Eval(”ToolTip”) %>’ /><br />
      ImageUrl:
      <asp:Label ID=”ImageUrlLabel” runat=”server” 
                 Text='<%# Eval(”ImageUrl”) %>’ /><br />
      <asp:Button ID=”DeleteButton” runat=”server” 
                  CommandName=”Delete” Text=”Delete” />
    </li>
   </ItemTemplate>
  <LayoutTemplate>
    <ul class=”ItemContainer”>
      <li ID=”itemPlaceholder” runat=”server” />
    </ul>
  </LayoutTemplate>
</asp:ListView>

12. Switch back to Design View, select the EntityDataSource control, and open its Properties Grid. Switch to the Events tab and double-click the Inserting event. At the top of the page add an Imports or a using statement for the PlanetWroxModel namespace, as you did in the other pages in this chapter. Then in the event handler that VS added for you, write the following code:

VB.NET

Protected Sub EntityDataSource1_Inserting(sender As Object, 
      e As EntityDataSourceChangingEventArgs) Handles EntityDataSource1.Inserting
  Dim photoAlbumId As Integer =
           Convert.ToInt32(Request.QueryString.Get(”PhotoAlbumId”))
  Dim myPicture As Picture = CType(e.Entity, Picture)
  myPicture.PhotoAlbumId = photoAlbumId
End Sub
 

C#

protected void EntityDataSource1_Inserting(object sender, 
      EntityDataSourceChangingEventArgs e)
{
  int photoAlbumId = Convert.ToInt32(Request.QueryString.Get(”PhotoAlbumId”));
  Picture myPicture = (Picture)e.Entity;
  myPicture.PhotoAlbumId = photoAlbumId;
}

13. Add the following CSS code to Monochrome.css in the Monochrome theme's folder and save the file:

.ItemContainer
{
  width: 600px;
  list-style-type: none;
  clear: both;
}
 
.ItemContainer li
{
  height: 300px;
  width: 200px;
  float: left;
}
 
.ItemContainer li img
{
  width: 180px;
  margin: 10px 20px 10px 0;
}

14. Add the same code to DarkGrey.css in the DarkGrey theme's folder, but this time set the width of the ItemContainer class to 400px like this:

.ItemContainer
{
  width: 400px;
  list-style-type: none;
  clear: both;
}

15. Save all your changes, close all open files, and then request NewPhotoAlbum.aspx in your browser. Make sure you don't accidentally open ManagePhotoAlbum.aspx, because it requires a query string that is sent by NewPhotoAlbum.aspx. Enter a new name for the photo album and click Insert. You're taken to ManagePhotoAlbum.aspx where you can enter new pictures. For now, all you can do is enter the description of the picture, the tooltip, and a fake URL of the image (just enter some text); you see later how to modify this and let a user upload real pictures to the website. Once you click the Insert button, a new item appears in the list, next to the insert controls. Add a few more items and you'll notice that the insert controls move to a row below the others, as shown in Figure 14.16, which shows the page in Firefox.

16. Click the Delete button for an item and see how the item is removed from the list automatically.

17. If you're currently viewing the site in the Monochrome theme, use the drop-down list to switch to DarkGrey. Notice that this theme shows only two images per row instead of the three displayed by the Monochrome theme shown in Figure 14.16.

Figure 14.13

14.13

Figure 14.14

14.14

Figure 14.15

14.15

Figure 14.16

14.16

How It Works

You started this exercise by attaching an EntityDataSource control to the ListView control. The EntityDataSource is configured to work with the Pictures entity set. As you saw earlier, each picture is linked to a photo album by its PhotoAlbumId. To have the ManagePhotoAlbum.aspx page display only those pictures that belong to the current photo album (identified by the PhotoAlbumId query string), you set up a WhereParameter:

<asp:EntityDataSource ID=”EntityDataSource1” runat=”server”
    ConnectionString=”name=PlanetWroxEntities”
    DefaultContainerName=”PlanetWroxEntities” EnableDelete=”True” 
    EnableFlattening=”False” EnableInsert=”True” EntitySetName=”Pictures” 
    Where=”it.PhotoAlbum.Id = @photoAlbumId” 
    OnInserting=”EntityDataSource1_Inserting”>
  <WhereParameters>
    <asp:QueryStringParameter Name=”PhotoAlbumId” 
         QueryStringField=”PhotoAlbumId” Type=”Int32” />
  </WhereParameters> 
</asp:EntityDataSource>

You should take note of two important parts in this markup. First, there's the WhereParameters collection that contains a QueryStringParameter that looks at the PhotoAlbumId field in the query string. When the EntityDataSource is about to get its data, it retrieves the value for the parameter from the query string.

The second important part is the Where attribute of the EntityDataSource control. It uses a Where clause to limit the items that are returned from the database:

Where=”it.PhotoAlbum.Id = @photoAlbumId”

This gets all the Pictures from the Picture table that have the requested PhotoAlbumId. The it in the Where clause is an implicit range variable, just like other range variables in queries you've seen before. However, here you need to use it and cannot make up your own name as you can do with your own LINQ queries. At run time, the Where clause is filled in with the actual PhotoAlbumId, retrieved from the query string, which ensures only pictures belonging to the current album are returned.

The first time the page loads after you create a new photo album, there won't be any pictures. However, as soon as you start adding items using the InsertTemplate of the ListView control, you'll see them appear in the list.

To display the pictures on the page, you used the ListView control. Just like other data-bound controls, the ListView is able to display repetitive data in a structured way. In this example, you set the ListView to bulleted list mode, so the control presents its data as a set of <li> elements. You define the container of the items with the <LayoutTemplate>:

<LayoutTemplate>
  <ul class=”ItemContainer”>
    <li ID=”itemPlaceholder” runat=”server” />
  </ul>
</LayoutTemplate>

Note that this <li> has its ID set to itemPlaceholder. This tells the ListView control where to add the individual pictures. At run time, this element will be replaced by the actual items from the templates, like <ItemTemplate>.

When the ListView control needs to display its data, it creates an item based on the <ItemTemplate> for each data item in the data source. In this example, each data item is a strongly typed Picture object, which provides access to properties such as ToolTip and Description:

<ItemTemplate>
  <li>
    ...
    ToolTip:
    <asp:Label ID=”ToolTipLabel” runat=”server” Text='<%# Eval(”ToolTip”) %>’ />
    ...
    <asp:Button ID=”DeleteButton” runat=”server” CommandName=”Delete” />
  </li>
</ItemTemplate>

With this code in place, each item in the data source is presented as a series of labels that display relevant properties of the picture. Eval(PropertyName) is used to retrieve the requested value from the object, which is then displayed as the Label control's text. Note that at this stage, the<ItemTemplate> only displays data about the picture. You see how to upload and display real pictures later.

Note the CommandName of the Button control in the ItemTemplate. It's set to Delete, which turns this button into a true Delete button. When you click it, the ListView figures out what picture you clicked the button for and then instructs the EntityDataSource control to delete the associated picture from the database.

The code you added to the theme's CSS files displays the items in an organized way. By setting the class attribute of the <ul> control to ItemContainer, the following CSS is applied to that list:

.ItemContainer
{
  width: 600px;
  list-style-type: none;
  clear: both;
}

The first property sets the entire width of the list to 600 pixels and the second declaration removes the bullet from the items in the list. Each item in the list is then displayed within a <li> element, to which the following CSS is applied:

.ItemContainer li
{
  height: 300px;
  width: 200px;
  float: left;
}

Each item gets a forced width of 200 pixels. The float property tells the <li> elements to float next to each other. Within the parent area of 600 pixels you can fit three <li> elements of 200 pixels each, causing the fourth and further elements to be placed on their own line. This is a great alternative to presenting data with HTML tables, which generally needs a lot more markup to achieve the same effect.

Finally, each image within the <li> element gets a forced width of 180 pixels and 10 pixels of margin at the top and bottom, 20 pixels on the right (to create some room between the images), and none at the left side:

.ItemContainer li img
{
  width: 180px;
  margin: 10px 20px 10px 0;
}

In the DarkGrey theme, the width of the ItemContainer is set to only 400 pixels. This way, the <div> is just wide enough to display two images side by side.

In contrast to many of the other data-bound controls, the ListView also supports inserting by defining an InsertItemTemplate that contains one or more controls that are bound to properties in the underlying object. For example, the Description property of the picture is bound like this:

<InsertItemTemplate>
  <li>
    Description:
    <asp:TextBox ID=”DescriptionTextBox” runat=”server” 
                     Text='<%# Bind(”Description”) %>’ /><br />
    ...
</InsertItemTemplate>

Instead of Eval(PropertyName), this code uses Bind(PropertyName) to set up a two-way binding mechanism. This ensures that the ASP.NET run time is able to figure out the relationship between the Description property of a Picture and the text box called DescriptionTextBox, even after a postback. So when you enter some details and click the special Insert button (with its CommandName set to Insert), a new Picture object is constructed, its properties, such as Description, Title, and ToolTip, are filled with the values from the associated server controls in theInsertItemTemplate, and then the Picture instance is forwarded to the EntityDataSource control, which takes care of saving the item in the database and refreshing the list of pictures that are displayed on the page.

Once the EntityDataSource control is about to save the picture, it fires its Inserting event. In that event handler in this exercise you added some code that linked the new Picture instance to the PhotoAlbumId like this:

VB.NET

  Dim photoAlbumId As Integer =
           Convert.ToInt32(Request.QueryString.Get(”PhotoAlbumId”))
  Dim myPicture As Picture = CType(e.Entity, Picture)
  myPicture.PhotoAlbumId = photoAlbumId
 

C#

  int photoAlbumId = Convert.ToInt32(Request.QueryString.Get(”PhotoAlbumId”));
  Picture myPicture = (Picture)e.Entity;
  myPicture.PhotoAlbumId = photoAlbumId;

The Include Foreign Key Columns option you enabled earlier in this chapter has given you a PhotoAlbumId property on the Picture class that enables you to directly set the ID of the PhotoAlbum (which you retrieved from the query string). This in turn relates the picture in the database to a specific photo album in the PhotoAlbum table. If you hadn't enabled that option, the property wouldn't have been there, and you would have had to assign a PhotoAlbum instance to the PhotoAlbum property of the picture by querying the photo album from the database based on its ID. Using the Foreign Key Columns option makes this process a lot easier because you can simply assign the ID of the album you want to add this picture to.

The code in the ListView uses Eval and Bind statements to get data in and out of the Picture objects that you're assigning to the controls. ASP.NET 4.5 introduces a new way to set up these bindings, as you see next.

Using Strongly Typed Data-Bound Controls

When VS created the code for the ListView for you, it added Bind and Eval statements like this:

<asp:TextBox ID=”ToolTipTextBox” runat=”server” Text='<%# Bind(”ToolTip”) %>’ />
...
<asp:Label ID=”ToolTipLabel” runat=”server” Text='<%# Eval(”ToolTip”) %>’ />

As you learned in the “How It Works” section of the preceding exercise, Bind is for two-way data binding (for insert and edit scenarios) and Eval is for read-only scenarios. Notice how the name of the property (ToolTip in this example) is a literal string placed between quotes. Using string literals makes your code more prone to errors. First of all, it's easy to misspell the name. Because a string cannot be checked by VS at development time, you won't notice this error until you view the page in the browser. Secondly, if you rename a property, the change is not picked up by the string literal.

Fortunately, ASP.NET 4.5 now has a solution to deal with this problem. The data-bound controls (such as the ListView, the Repeater, the GridView, the DetailsView, and the FormView) have been turned into strongly typed data-bound controls. They have been extended with an ItemTypeproperty that you can point to the type of object you're assigning to its data source. In the preceding example, this type would have been PlanetWroxModel.Picture, the fully qualified name of the Picture class. Once you've set this property, the data-bound control gets two new properties, Item andBindItem, that are of the type you assigned to the ItemType property. The first one serves as a replacement for Eval and the second one replaces Bind. By setting the ItemType property and using Item and BindItem, you get the following benefits:

· IntelliSense now helps you find the correct property of the object you're working with, as shown in Figure 14.17.

Figure 14.17

14.17

· When you now misspell the name of a property, or rename it later, you get an error in the Error List, giving you the chance to fix the problem before the page is viewed in the browser.

· External tools (available for the full versions of Visual Studio) will correctly rename the item in the markup when you rename a property on your object.

Although this is an excellent new feature that will help you write better code, you can only use this solution when working with a strongly typed object such as Picture from an EF model, or from other classes you or third-party developers create. It won't work in situations such as the following:

· When using the SqlDataSource control. This control uses an object under the hood that doesn't expose strongly typed properties that map to columns (such as ToolTip or Description), and as such you cannot access these columns or properties through the Item and BindItem properties.

· When using anonymous objects. Because the anonymous object doesn't have a name, you cannot assign its name to the ItemType property.

In the following short exercise, you see how to make use of this new strongly typed data-binding capability.

Try It Out: Using Strongly Typed Data-Bound Controls

In this exercise you modify the ListView control in ManagePhotoAlbum.aspx by making it strongly typed. You see how to set the ItemType property and how to replace Bind and Eval with their strongly typed counterparts. When you're done, the page will work exactly as before, but will now be easier to maintain in the future.

1. Start by opening ManagePhotoAlbum.aspx in Markup View in VS.

2. Locate the opening tag of the ListView and add an ItemType property with its value set to PlanetWroxModel.Picture. Notice how IntelliSense helps you find the correct item by showing a list with all the types that can be used as the ItemType, as shown in Figure 14.18.

3. Inside the InsertItemTemplate template of the ListView, locate the Bind statement for the Description TextBox, and change it from Bind(”Description”) to BindItem.Description. You should end up with this code:

<asp:TextBox ID=”DescriptionTextBox” runat=”server” 
       Text='<%# BindItem.Description %>’ />

4. Repeat the previous step for the ToolTip TextBox. The ImageUrl gets special treatment later in this chapter, so there's no point in changing it now.

5. Inside the ItemTemplate template of the ListView, locate the Eval statement for the Description Label and change it from Eval(”Description”) to Item.Description. You should end up with this code:

<asp:Label ID=”DescriptionLabel” runat=”server” 
           Text='<%# Item.Description%>’ />

6. Repeat step 5 for the ToolTip Label. Again, the ImageUrl gets special treatment later in this chapter, so there's no point in changing it now.

7. Save your changes and request NewPhotoAlbum.aspx in the browser. Insert a new album and then add a few pictures by entering a description and tooltip and uploading an image file. Notice how the code still behaves as before.

Figure 14.18

14.18

How It Works

You didn't have to change a lot to make the ListView strongly typed and get access to IntelliSense and compile-time checking of your code. All you had to do was add an ItemType property to the ListView and replace the calls to Bind and Eval with their BindItem and Item counterparts. With these changes in place, the control behaves exactly as before. At run time, the control looks at the value of the assigned properties and displays them in the browser. When you submit data back to the server, the reverse takes place: The values you entered in the controls are assigned to the properties of an instance of the Picture object, which is then saved in the database by the Entity Framework. It's recommended to use the strongly typed binding capabilities whenever you can, because they'll help you spot errors much sooner and make it easier to type your code.

Right now, users need to type in a URL for an image manually. Obviously, this isn't very user friendly. It would be much easier for them if they could pick an image from their local computer and upload it to the server. You see how to accomplish this in the next exercise.

Try It Out: Customizing Templates of the ListView Control

The default templates for the ListView control that VS generates based on the information from the EntityDataSource are enough only in the most trivial situations. Usually, you need much more control. For example, in the ItemTemplate you may want to display an actual Image control instead of the plain ImageUrl property as text. Likewise, in the InsertItemTemplate you may want to display a file upload control instead of a simple text box. In this exercise, you see how to change the standard templates so you can incorporate both features. Additionally, you see how to handle the Inserting event of the EntityDataSource control to save the uploaded file to disk, and update the database with the URL of the image.

For this example to work, the account used by the web server (the account you use to log on to your machine if you are using IIS Express) needs read and write permissions to the GigPics folder that you create in this exercise. The account should already have these permissions on your machine, but if you run into problems with this exercise, refer to the section “Understanding Security in IIS” in Chapter 19.

1. Create a new folder in the root of the website called GigPics. This folder will contain concert pictures uploaded by users.

2. Open the ManagePhotoAlbum.aspx page in Markup View and locate the <ItemTemplate> element. Remove the Label that displays the ImageUrl and replace it with an Image control, with its ImageUrl set to the ImageUrl of the picture object.

<asp:Image ID=”ImageUrl” runat=”server” ImageUrl='<%# Item.ImageUrl %>’ />

Remove the text ImageUrl: that appears right above the image.

3. To enable users to upload images, you need to replace the TextBox for the ImageUrl property with a FileUpload control. You also need to remove the text ImageUrl: again. You do this in the InsertItemTemplate:

<asp:TextBox ID=”ToolTipTextBox” runat=”server” 
                Text='<%# BindItem.ToolTip %>’ /><br />
<asp:FileUpload ID=”FileUpload1” runat=”server” />
<br />
<asp:Button ID=”InsertButton” runat=”server” CommandName=”Insert” Text=”Insert” />

Note that you don't need to bind the property to the control here. Because the uploaded image needs special treatment, you'll write some code in the Code Behind of the page instead of relying on the built-in data-binding capabilities.

4. Set the CausesValidation property of the Cancel button in the <InsertItemTemplate> to false:

<asp:Button ID=”CancelButton” runat=”server” CommandName=”Cancel” Text=”Clear” 
          CausesValidation=”false” />

5. Similarly, set the CausesValidation property of the Delete button in the <ItemTemplate> to false.

6. Switch to the Code Behind of the page (press F7) and then extend the Inserting event handler with the following code, which saves the file to disk and then updates the ImageUrl property of the Picture instance with its new location:

VB.NET

myPicture.PhotoAlbumId = photoAlbumId
 
Dim FileUpload1 As FileUpload =
              CType(ListView1.InsertItem.FindControl(”FileUpload1”), FileUpload)
Dim virtualFolder As String = "∼/GigPics/”
Dim physicalFolder As String = Server.MapPath(virtualFolder)
Dim fileName As String = Guid.NewGuid().ToString()
Dim extension As String = System.IO.Path.GetExtension(FileUpload1.FileName)
 
FileUpload1.SaveAs(System.IO.Path.Combine(physicalFolder, fileName + extension))
myPicture.ImageUrl = virtualFolder + fileName + extension
 

C#

myPicture.PhotoAlbumId = photoAlbumId;
 
FileUpload FileUpload1 =
              (FileUpload)ListView1.InsertItem.FindControl(”FileUpload1”);
string virtualFolder = "∼/GigPics/”;
string physicalFolder = Server.MapPath(virtualFolder);
string fileName = Guid.NewGuid().ToString();
string extension = System.IO.Path.GetExtension(FileUpload1.FileName);
 
FileUpload1.SaveAs(System.IO.Path.Combine(physicalFolder, fileName + extension));
myPicture.ImageUrl = virtualFolder + fileName + extension;

7. Go back to Markup View and add three validation controls to the InsertItemTemplate: two RequiredFieldValidator controls hooked up to the text boxes for the Description and ToolTip, and one CustomValidator with its ErrorMessage set to Select a valid .jpg file. Give theRequiredFieldValidator controls an ID such as reqDesc and reqToolTip and assign the CustomValidator an ID of cusValImage. Finally, set the TextMode property of the text box for the Description to MultiLine and enter a line break (a <br />) before the Insert button.

You should end up with the following code:

Description:
<asp:RequiredFieldValidator ID=”reqDesc” ControlToValidate=”DescriptionTextBox”
      runat=”server” ErrorMessage=”Enter a description.” />
<asp:TextBox ID=”DescriptionTextBox” runat=”server” TextMode=”MultiLine”
       Text='<%# BindItem.Description %>’ /><br />
ToolTip:
<asp:RequiredFieldValidator ID=”reqToolTip” ControlToValidate=”ToolTipTextBox”
      runat=”server” ErrorMessage=”Enter a tool tip.” />
<asp:TextBox ID=”ToolTipTextBox” runat=”server” 
       Text='<%# BindItem.ToolTip %>’ /><br />
<asp:FileUpload ID=”FileUpload1” runat=”server” /><br />
<asp:CustomValidator ID=”cusValImage” runat=”server”
       ErrorMessage=”Select a valid .jpg file.” />
<br />
<asp:Button ID=”InsertButton” runat=”server” CommandName=”Insert” Text=”Insert” />

8. Select the ListView control in Design View and set up an event handler for its ItemInserting event by double-clicking the event in the Events tab of the Properties Grid. Complete the event handler with the following code:

VB.NET

Protected Sub ListView1_ItemInserting(sender As Object, 
           e As ListViewInsertEventArgs) Handles ListView1.ItemInserting
  Dim FileUpload1 As FileUpload =
              CType(ListView1.InsertItem.FindControl(”FileUpload1”), FileUpload)
  If Not FileUpload1.HasFile OrElse
              Not FileUpload1.FileName.ToLower().EndsWith(”.jpg”) Then
    Dim cusValImage As CustomValidator =
      CType(ListView1.InsertItem.FindControl(”cusValImage”), CustomValidator)
    cusValImage.IsValid = False
    e.Cancel = True
  End If
End Sub
 

C#

protected void ListView1_ItemInserting(object sender, ListViewInsertEventArgs e)
{
  FileUpload FileUpload1 =
         (FileUpload)ListView1.InsertItem.FindControl(”FileUpload1”);
  if (!FileUpload1.HasFile || !FileUpload1.FileName.ToLower().EndsWith(”.jpg”))
  {
    CustomValidator cusValImage =
          (CustomValidator)ListView1.InsertItem.FindControl(”cusValImage”);
    cusValImage.IsValid = false;
    e.Cancel = true;
  }
}

9. Save all your changes, and then request NewPhotoAlbum.aspx in your browser (don't accidentally request the ManagePhotoAlbum.aspx page that you just worked on). Enter a new name for the photo album and click the Insert link. Insert a few pictures by entering a description and a tooltip, selecting a .jpg picture from your hard drive, and clicking the Insert button. Then enter the description and tooltip of another image, but leave the file upload box empty. When you click Insert, you get an error message indicating that you didn't upload a valid .jpg file, as shown inFigure 14.19.

10. Click the Browse button of the file upload box, browse for a valid .jpg file, and click the Insert button once more. The file now gets uploaded successfully.

Figure 14.19

14.19

How It Works

You haven't changed much in the actual process of inserting the Picture into the database. The ListView control still collects all the relevant data from the page and then sends it to the EntityDataSource control, which then inserts the item in the Picture table in the database through EF. What is different is the way you set up the templates and the way you handled the events of the EntityDataSource and ListView controls. Look at the templates first. Inside the ItemTemplate you added an <asp:Image> to take the place of the plain text label. As you can see in Figure 14.19, this displays the actual image, rather than just its URL.

To enable a user to upload the images, you replaced the TextBox control in the InsertItemTemplate with a FileUpload control. Additionally, you added a few validation controls to force the user to enter the required fields. As soon as you click the Insert button, the page posts back and theListView control fires its ItemInserting event. This event is a good place to perform any custom validation. One of the arguments this event handler receives (the e argument) is of the type ListViewInsertEventArgs, a class that provides context-sensitive information to the ItemInsertingevent. When you detect an error, you can set the Cancel property of this e argument to True (true in C#) to tell the ListView control you want to cancel the insert operation. Inside this event handler you added some code that “finds” the upload control in the InsertItem template. Because you can potentially have multiple controls with the same name (for example, a FileUpload control in the InsertItemTemplate and one in the EditItemTemplate), you cannot access FileUpload1 directly. Instead, you need to use FindControl on the InsertItem object to search for the control:

VB.NET

Dim FileUpload1 As FileUpload = 
          CType(ListView1.InsertItem.FindControl(”FileUpload1”), FileUpload)
 

C#

FileUpload FileUpload1 = 
     (FileUpload)ListView1.InsertItem.FindControl(”FileUpload1”);

When you have a reference to the FileUpload control, you can check its HasFile property to see if a file has been uploaded. Additionally, you can check FileUpload1.FileName.ToLower().EndsWith(”.jpg”) to see if a file with a .jpg extension has been uploaded. To ensure that this test is carried out only when the user has uploaded a file, the code uses OrElse in VB and || in C# to short-circuit the logic in the If statement, as explained in Chapter 5.

If the user doesn't upload a valid file, the code in the If block runs. It uses FindControl again to find the CustomValidator control and sets its IsValid property to False (false in C#). This tells the control to display its ErrorMessage property when the page renders. Finally, to stop theListView from continuing the insert operation you need to set the Cancel property of the e argument to True (true in C#):

VB.NET

e.Cancel = True
 

C#

e.Cancel = true;

The other validation controls to make sure a title and description are entered work in the same way as you saw in Chapter 9.

If the user uploaded a valid .jpg file, the ListView continues with its insert operation, which eventually results in an insert action against the EntityDataSource control. When that control is about to send the insert operation to EF, it fires its Inserting event, giving you a last chance to hook into the process and look at the data. Inside this event handler, you used similar code to find a reference to the FileUpload control inside the InsertItem template. You then used the following code to determine the physical and virtual folder for the file, its name, and its extension:

VB.NET

Dim virtualFolder As String = "∼/GigPics/”
Dim physicalFolder As String = Server.MapPath(virtualFolder)
Dim fileName As String = Guid.NewGuid().ToString()
Dim extension As String = System.IO.Path.GetExtension(FileUpload1.FileName)
 

C#

string virtualFolder = "∼/GigPics/”;
string physicalFolder = Server.MapPath(virtualFolder);
string fileName = Guid.NewGuid().ToString();
string extension = System.IO.Path.GetExtension(FileUpload1.FileName);

The variable virtualFolder holds the virtual location — starting off the root of the website — of the folder where the uploaded images are stored. Using Server.MapPath you can turn this into a physical folder. Assuming you have your project in its default location of C:\BegASPNET\Site, the physicalFolder variable now contains C:\BegASPNET\Site\GigPics.

Next, a new, random filename is generated using Guid.NewGuid(). The Guid class is able to generate more or less random filenames that are guaranteed to be unique across time and space. This code assigns the variable fileName something like f6d8ed05-2dbe-4aed-868a-de045f9462e3, which guarantees a unique filename. Finally, the extension of the file is retrieved using the static GetExtension method of the Path class in the System.IO namespace.

At this stage, you have all the required information to store the file on disk, and then update the database. Storing the file on disk is easy using the SaveAs method of the FileUpload control:

VB.NET

FileUpload1.SaveAs(System.IO.Path.Combine(physicalFolder, fileName + extension))
 

C#

FileUpload1.SaveAs(System.IO.Path.Combine(physicalFolder, fileName + extension));

This code takes the physical folder, the filename, and the extension and passes them to the Combine method of the Path class that builds up the full path. This path is then sent to the SaveAs method, which saves the file at the requested location.

Finally, the Picture instance is updated with the new ImageUrl:

VB.NET

myPicture.ImageUrl = virtualFolder + fileName + extension
 

C#

myPicture.ImageUrl = virtualFolder + fileName + extension;

This assigns something like ∼/GigPics/f6d8ed05-2dbe-4aed-868a-de045f9462e3.jpg to the ImageUrl property, which is the new virtual location of the uploaded image. Right after you insert the new image, the ListView is updated and now shows the new image, using the Image control with its ImageUrl set to the image you just uploaded.

You can imagine that if you upload a large number of images for a single photo album, the page becomes more difficult to manage. This is especially true at the front end, where users may be accessing your site over a slow network connection. Instead of presenting them all the images in the photo album on a single page, you can split up the photo album into multiple pages, enabling users to go from page to page. You see how to do this in the next section, which discusses the DataPager control.

Introducing the DataPager Control

The DataPager is a separate control that you can use to extend another, data-bound, control. Currently, the .NET Framework lets you use the DataPager only to provide paging capabilities to the ListView control, but the developer community has been active writing implementations for other controls, like the GridView, as well.

You can hook up the DataPager to the ListView control in two ways: You can either define it within the <LayoutTemplate> of the ListView control or you can define it entirely outside the ListView. In the first case, the DataPager knows to what control it should provide paging capabilities automatically. In the latter case, you need to set the PagedControlID property of the DataPager to the ID of a valid ListView control. You see how to configure and use the DataPager in conjunction with a ListView next. Being able to define the DataPager outside of the ListView control is useful if you want to place it at a different location of the page, such as in the Footer or Sidebar area, for example.

Try It Out: Paging Data with the ListView and DataPager Controls

In this Try It Out you create the front-end page of the Gig Pics feature. Users of your site can choose one of the available photo albums from a drop-down list and then view all the available pictures in a pageable list that is created by a ListView and a DataPager control. Figure 14.22 shows the final result of this exercise.

1. In the root of your site, create a new folder called PhotoAlbums. Inside this folder create a new Web Form based on your custom page template and call it Default.aspx. Set the Title of the page to All Photo Albums.

2. Switch to Design View and drop a DropDownList control on the page. On the control's Smart Tasks panel, enable AutoPostBack and then hook it up to a new EntityDataSource control by clicking Choose Data Source, and then choosing <New data source> from the first drop-down list. Click the Entity icon, click OK, and select PlanetWroxEntities in the Named Connection drop-down list. Click Next. On the Configure Data Selection dialog box, choose PhotoAlbums from the EntitySetName drop-down list and select the fields Id and Name, as shown in Figure 14.20.

3. Click Finish to close the Configure Data Source wizard. In the Choose a Data Source dialog box, select Name from the Data Field to Display drop-down list and leave Id selected in the Data Field for the Value drop-down list. If you don't see the items in the lists, click Refresh Schema first.

4. Click OK to close the Data Source Configuration Wizard.

5. Below the DropDownList add a new ListView control and connect it to a new EntityDataSource by selecting <New data source> from the drop-down list on the control's Smart Tasks panel. Click the Entity icon and click OK. Select PlanetWroxEntities in the drop-down and click Next. In the dialog box that follows, choose Pictures from the EntitySetName drop-down list and click Finish.

6. Select the new EntityDataSource control (called EntityDataSource2) in Design View, open its Properties Grid, and click the button with the ellipsis for the Where property to open up the Expression Editor. Check off the check box for an automatically generated Where clause at the top of the dialog box. Click the Add Parameter button and enter PhotoAlbumId as the name for the parameter. Then choose Control in the Parameter Source drop-down property and choose DropDownList1 as the ControlID. Finally, click the Show Advanced Properties link and change theType of the parameter to Int32. Your Expression Editor should look like Figure 14.21.

Click OK to close the dialog box.

7. On the ListView control's Smart Tasks panel, click Configure ListView (click Refresh Schema and reopen the Smart Tasks panel first if you don't see that option). Select Bulleted List as the layout, and in the Options area choose Enable Paging. The drop-down list below it should default to Next/Previous Pager, which is fine for this exercise.

8. Click OK and VS creates a couple of templates for you.

9. Switch to Markup View and add an ItemType attribute to the ListView with its value set to PlanetWroxModel.Picture. Your ListView should end up like this:

<asp:ListView ID=”ListView1” runat=”server” DataKeyNames=”Id”
      DataSourceID=”EntityDataSource2” ItemType=”PlanetWroxModel.Picture”>

10. Next, remove the following templates and the code they contain:

· <AlternatingItemTemplate>

· <EditItemTemplate>

· <InsertItemTemplate>

· <ItemSeparatorTemplate>

· <SelectedItemTemplate>

11. Remove the ID, runat, and style attributes from the <ul> in the LayoutTemplate and add a class attribute and set it to ItemContainer. Locate the DataPager inside the LayoutTemplate and add a PageSize attribute set to 3. Finally, add clear: both; to the empty style attribute:

<LayoutTemplate>
  <ul class=”ItemContainer”>
    <li id=”itemPlaceholder” runat=”server” />
  </ul>
  <div style=”clear: both;”>
    <asp:DataPager ID=”DataPager1” runat=”server” PageSize=”3”>
      <Fields>
        <asp:NextPreviousPagerField ButtonType=”Button” ShowFirstPageButton=”True”
                ShowLastPageButton=”True” />
      </Fields>
    </asp:DataPager>
  </div>
</LayoutTemplate>

12. Modify the code in the ItemTemplate so it ends up like this:

<ItemTemplate>
  <li>
    <asp:Image ID=”Image1” runat=”server” ImageUrl='<%# Item.ImageUrl %>'
            ToolTip='<%# Item.ToolTip %>’ />
    <asp:Label ID=”DescriptionLabel” runat=”server”
            Text='<%# Item.Description %>’ />
  </li>
</ItemTemplate>

This creates an Image control with its ImageUrl and ToolTip properties bound to the corresponding properties of the Picture object that you're binding to. The ToolTip appears when you hover your mouse over the image in the browser. Below the image, a simple Label control displays the Description of the image. You don't need the other properties that were initially defined in the template for this exercise.

13. Next, wrap the entire code in the cpMainContent content block in an UpdatePanel with a ContentTemplate element to avoid page flicker when paging the list of pictures, or when choosing a new photo album from the list.

<asp:Content ID=”Content2” ContentPlaceHolderID=”cpMainContent” runat=”Server”>
  <asp:UpdatePanel ID=”UpdatePanel1” runat=”server”>
    <ContentTemplate>
      <asp:DropDownList ID=”DropDownList1” runat=”server” AutoPostBack=”True” 
        DataSourceID=”EntityDataSource1” DataTextField=”Name” DataValueField=”Id”>
      ...
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>

14. Open the Web.sitemap file for the site and add a main menu and two submenu items for the Gig Pics section, between the Reviews and About sections:

</siteMapNode>
<siteMapNode url=”∼/PhotoAlbums/” title=”Gig Pics” description=”All Gig Pics”>
  <siteMapNode url=”∼/PhotoAlbums/Default.aspx” title=”Gig Pics”
           description=”All Gig Pics” />
  <siteMapNode url=”∼/NewPhotoAlbum.aspx” title=”New Album”
           description=”Create a new Photo Album with Gig Pics” />
</siteMapNode>
<siteMapNode url=”∼/About/Default.aspx” title=”About” 
             description=”About this site”>

15. Because you added another menu item, you need to change the width of each item in the menu for the Monochrome theme. To do this, open up Monochrome.css and change the width for the .MainMenu ul li selector from 200 to 160 pixels:

.MainMenu ul li
{
  width: 160px;
}

16. Save all your changes and then request Default.aspx from the PhotoAlbums folder in your browser. Choose a photo album from the drop-down list and the page reloads, showing you the relevant pictures in the photo album.

Figure 14.20

14.20

Figure 14.21

14.21

COMMON MISTAKES

If nothing happens when you choose a new item from the drop-down list, go back to VS and ensure you set AutoPostBack on the DropDownList control to True.

If you don't have any pictures in a photo album, or not enough to fill an entire page, choose New Album from the Gig Pics menu, create a new photo album, and add at least four images to it. Then click the Gig Pics menu item and choose your new photo album from the drop-down list. Note that there is now a paging user interface, enabling you to move forward and backward through the list of pictures in the photo album using the First, Previous, Next, and Last buttons visible at the bottom of the screen in Figure 14.22. Because of the AJAX panel you added, the selection and paging operations now occur completely flicker-free.

Note

Your first few albums will end up broken. That's because you didn't supply images when you created them. You can delete the albums (and their associated picture rows) from the database if you want to clean up a bit. In Chapter 16 you develop functionality to delete images from the Default.aspx page in the PhotoAlbums folder.

Figure 14.22

14.22

How It Works

Most of what you have seen in this exercise should be familiar. You connected a DropDownList to an EntityDataSource using a method similar to the one you used to create the Genres drop-down list in the previous chapter. The ListView and its associated EntityDataSource are also similar to what is discussed earlier in this chapter. However, instead of a Where parameter that looks at the query string, the code now uses a Where parameter that looks at the DropDownList:

<WhereParameters>
  <asp:ControlParameter ControlID=”DropDownList1” Name=”PhotoAlbumId” 
           PropertyName=”SelectedValue” Type=”Int32” />
</WhereParameters>

Because you set AutoGenerateWhereClause to True on the EntityDataSource control, a Where clause is created on the fly based on these parameters. This is in contrast to an earlier example where you explicitly defined a Where clause. For simple scenarios as in the preceding exercise, relying on the automatically generated Where clause works fine. For more detailed scenarios, it's good to know you can also assign one explicitly.

When the EntityDataSource is about to get its data from the database, it looks at the SelectedValue property of the drop-down list and then retrieves only the pictures that match the requested PhotoAlbumId.

The biggest difference from previous examples is the addition of the DataPager. As demonstrated in this exercise, paging is handled for you automatically. All you need to do is embed a DataPager control somewhere in the LayoutTemplate of the ListView and the rest is taken care of automatically. If you place the DataPager outside the ListView, don't forget to hook it up to the ListView by setting the PagedControlID property. If you prefer links or images over buttons, you can set the ButtonType property of the NextPreviousPagerField element to Link or Image, respectively. If you prefer a numeric pager, replace the NextPreviousPagerField item with a NumericPagerField:

<asp:NumericPagerField NextPageText=”...” PreviousPageText=”...” />

In this exercise you set the PageSize to 3 so it's easier to fill more than one page and see the pager at work. In real-world applications the PageSize is usually a bit higher, such as 10 or 20. Because the layout uses a three-column layout for the Monochrome theme and a two-column layout for the DarkGrey theme, you may want to select a value that's dividable by both, such as 18.

A Few Notes about Performance

The preceding exercise has two known performance problems that you should be aware of. First, although the ItemTemplate of the ListView resizes the images to 180 pixels in the browser by setting their widths through CSS, the actual image is left unmodified. This means that if you upload a large image, the entire image is still downloaded, only to display as a small thumbnail image. It would be better to create a true thumbnail image at the server, and send that to the browser instead. The chapter entitled “Greeting Cards” in my book ASP.NET 2.0 Instant Results (ISBN: 978-0-471-74951-6) has a number of examples on resizing images on the server.

The other potential performance problem lies in the way the data is paged. With the DataPager control, the data is paged inside the ASPX page. That means that all data is retrieved from the database and sent to the EntityDataSource control. The DataPager control then selects the right rows to display on the current page. This works fine for result sets of up to hundreds of rows. However, as soon as the number of items in a photo album or other collection grows above that number, you may find that your pages start to slow down. If that's the case, you may want to look at paging at the database level. The ADO.NET Entity Framework supports this scenario using the Skip() and Take() methods you've seen before.

Practical LINQ and ADO.NET Entity Framework Tips

Here are some practical LINQ and ADO.NET Entity Framework tips:

· In this chapter you saw how to create anonymous types to shape the data you want to return from your queries. The compiler and IntelliSense are invaluable tools in determining what data you can return and what properties you have available. Spend some time playing around with the anonymous types, looking at the different options that the IntelliSense lists give you.

· Just as with other data access methods, like the SqlDataSource control you saw in the previous chapter, try to filter your data as much as possible. If you know you only need reviews in the Jazz genre, be explicit and incorporate a Where clause in your code or EntityDataSource control that limits the list of reviews at the database level. This speeds up your queries and data retrieval, improving the overall speed of the application.

· Make use of anonymous types to decrease the memory consumption of your LINQ queries. For example, instead of retrieving the entire Review object, use the New (new in C#) keyword to create a new anonymous type on the fly. Because this new object contains only the properties you really need, you save yourself the overhead of bringing in the full object.

Summary

LINQ is a compelling and exciting technology that ships with .NET. LINQ is an important plumbing technique in many data access scenarios, including database access in ASP.NET web applications using the ADO.NET Entity Framework.

Because LINQ is so important, it has been integrated in many different places in .NET. LINQ is available for objects enabling you to query in-memory collections. Additionally, LINQ is available for XML, ADO.NET, and DataSets, each type providing access to a different data store, but with the same, unified querying language. LINQ is also used as the query language for the ADO.NET Entity Framework.

To work with EF in your ASP.NET web applications you have a couple of different options. First, you can write queries in the Code Behind of a page and then bind the results to a data-bound control using the DataSource property and DataBind method of the control. Alternatively, you can use the EntityDataSource control that serves as the bridge between your data-bound controls and your model. Combined with the new ListView and DataPager controls, the EntityDataSource enables you to create fully functional CRUD pages.

Until now, the database-driven pages you have seen look quite dull. You haven't applied a lot of styling, or provided any conditional formatting, where data is presented differently based on its values. You can do this through control styles and the many events of the data-bound and data source controls. The next chapter shows you how to make use of these styles and events.

Exercises

1. Imagine you have a page in the Reviews folder called MostRecent.aspx. This page should show the 10 most recently added reviews. What would your LINQ query look like if you only wanted to show the review's Title property and the name of the genre it belongs to? You should use theTake method to limit the result set to 10. If you're having trouble writing the code to get the last reviews, look at the section titled “First, FirstOrDefault, Last, and LastOrDefault,” which shows you how to get the last review in the database.

2. What is the major benefit of the ListView control over other data controls like GridView and Repeater?

3. Currently the Default.aspx page from the PhotoAlbums folder just shows the thumbnails of the pictures. What would you need to do to display the full-size picture on its own page using a LINQ query?

4. When you delete a picture from the ListView on the Photo Album page, only the database row is deleted, but the image on disk is left untouched. Make use of the static System.IO.File.Delete method to delete the item from disk. Choose an appropriate event of the EntityDataSource to handle this and make use of the Entity property that is available on the e argument of the event.

5. Currently, the AllByGenre.aspx page displays the title of the genre, regardless of whether it has any reviews attached to it. How can you hide genres that don't have any reviews? Make use of the Count method on the Reviews collection to solve this question.

You can find answers to these exercises in Appendix A.

What You Learned in this Chapter

ADO.NET Entity Data Model file

The file that contains the information necessary to map your object model to the tables in your database

Anonymous types

Types that are created on the fly without defining them explicitly

Entity Framework

A technology to create a strongly typed object model with an underlying database that enables you to interact with the data in your database

Entity sets

A collection of objects in your entity model; for example, a PhotoAlbum instance has a Pictures entity set that contains the pictures in the album.

EntityDataSource control

An ASP.NET control that serves as a bridge between your ASPX pages and the Entity Framework

Lazy loading

A technique in which data is not loaded from the database until it is accessed at run time

LINQ

Language-Integrated Query; the part of .NET Framework programming languages that provides querying support against all kinds of data collections, including objects, XML, and databases

Range variable

A variable defined in your LINQ query that is subsequently used in the Select and Where parts

Strong typing

A programming concept in which the type of a variable is explicitly defined when the variable is declared

Type inference

A technique in which the compiler determines the type of a variable based on the data it gets assigned; this enables you to create strongly typed variables without explicitly defining the variable's type first.