Chapter 13. LINQ in every layer

This chapter covers:

  • The LinqBooks application
  • N-tier architecture
  • Third-party LINQ providers
  • The future of LINQ

Congratulations! You’ve reached the last chapter of this book. You should now have a good grasp of LINQ’s capabilities and should now be able to put the skills you’ve acquired into practice in your projects. There is still one last subject we’d like to cover: the place of LINQ in your applications.

As you know, LINQ is not only a generic querying technology but a complete set of tools you can use to deal with relational databases, XML, DataSets, in-memory object collections, and many more data sources thanks to LINQ’s extensibility. This means that from now on, LINQ is likely to become pervasive in your code.

In this chapter, we will look at a sample application we’ve created using LINQ. This application is the LinqBooks running example that we introduced in chapter 4 and that we used over this book’s chapters as the base for the code samples. You’ll be able to find the complete source code in the downloadable package that accompanies this book. By looking at the LinqBooks sample, you’ll be able to identify where and how each LINQ flavor is used. Our goal is to help you decide whether you need to use LINQ in your application layers, as well as see what impact it can have on your applications’ architecture.

We’ll start by describing the LinqBooks application. We’ll then focus on the place LINQ to SQL has in this application. LINQ to SQL is likely to influence your application’s architecture, so it’s important to spend time thinking about how to use it. Once we’re done with LINQ to SQL, we’ll analyze where LINQ to XML and LINQ to DataSet can be useful. Finally, we’ll use LINQ’s extensibility through custom query operators as well as our LINQ to Amazon implementation.

13.1. Overview of the LinqBooks application

We already introduced our sample application in chapter 4. Let’s present it again, but with an accent on the architecture and the use of LINQ. The LinqBooks application allows its users to manage a personal book catalog.

13.1.1. Features

Here are the main features of the LinqBooks application:

  • Tracking books users own
  • Storing what users think about them
  • Retrieving more information about books from external sources
  • Publishing the list of books and review information to make it available to others

The technical features implemented include

  • Adding books, publishers, and authors to the catalog
  • Attaching books to publishers and authors
  • Adding reviews to books
  • Querying/inserting/updating data in a local SQL Server database
  • Searching over the local catalog
  • Searching over Amazon’s catalog
  • Importing data about books from Amazon
  • Importing and persisting some data from/as XML documents
  • Creating RSS feeds for the books you recommend

Let’s now get an idea of the UI that exposes these features.

13.1.2. Overview of the UI

We decided to implement LinqBooks as a web application. It also comes with a utility for importing data from Amazon that is implemented as a Windows Forms application.

Let’s see some screenshots of the application. Figure 13.1 shows the page that displays the list of books from the database.

Figure 13.1. Web page that displays the list of books in a grid as well as some statistics

The page that displays the details for a book can be seen in figure 13.2.

Figure 13.2. Web page that displays the details about a book and allows us to add authors and reviews

Figure 13.3 shows the page that displays the list of publishers.

Figure 13.3. Web page that displays the list of publishers and their books in a grid

There are more pages in the LinqBooks application, of course. You’ll discover some in the rest of this chapter, and all of them if you look at the source code and run the application.

Let’s now give you an overview of the data model used by the sample application.

13.1.3. The data model

The LinqBooks application relies on a SQL Server database. The database schema we use is shown in figure 13.4.

Figure 13.4. Database schema for the running example

Now that you have a good idea of what the LinqBooks application does, we can focus on the use of all the LINQ flavors in all of the application’s layers. We’ll start with LINQ to SQL and we’ll analyze how it influences the architecture of applications that employ it.

13.2. LINQ to SQL and the data access layer

The flavor of LINQ that may have the biggest impact on application architecture is probably LINQ to SQL. This is why it’s worth spending time thinking about the traditional three-tier architecture and how LINQ to SQL fits in the picture.

The impact of the use of LINQ to SQL may be so profound that we may have to reconsider the very nature of a data access layer.

Let’s start with a refresher on the traditional three-tier architecture and data access layer. We’ll then throw LINQ to SQL in and analyze the impacts on such architectures. Finally, we’ll depict the way we use LINQ to SQL in LinqBooks through some code samples.

13.2.1. Refresher on the traditional three-tier architecture

You probably already know how a multitier architecture is structured, but it’s good to quickly go over the basic principles to ensure that we’re speaking about the same things. While you can use several tiers in a multitier architecture, we’ll focus here on the three-tier architecture because it’s sufficient for discussing the impact of LINQ to SQL on such an architecture.

A three-tier architecture is any system that enforces a general separation between the following three parts:

  • The presentation tier
  • The logic tier or business logic tier
  • The data access tier

 

Note

In this discussion, the words tier and layer are used to refer to the same thing.

 

Figure 13.5 shows the elements parts of a traditional three-tier architecture.

Figure 13.5. The traditional three-tier application architecture, with its presentation, business logic, and data access layers, as well as the optional object models that may be used with it

Dividing an application into several tiers or layers as described in the figure is all about separation of concerns. The goals of separation of concerns are to design systems so that functions can be optimized independently of other functions, so that failure of one function does not cause other functions to fail, and in general to make it easier to understand, design, and manage complex interdependent systems. This is why a common practice is to break a program into distinct layers that overlap as little as possible.

Let’s review the role of each of the layers in the three-tier architecture:

  • The data-access layer stores and retrieves information from a database, file system, or any other storage. The information is passed back to the business logic layer for processing and eventually back to the user.
  • The business logic layer coordinates the application, processes commands, makes logical decisions and evaluations, and performs calculations. It also moves and processes data between the two surrounding layers.
  • The presentation layer handles the topmost level of the application: the user interface. The main function of the interface is to translate tasks and results into something the user can understand. The presentation layer contains components needed to interact with the user of the application. Examples of such components are web pages and rich-client forms.

The object models shown in the figure are optional. They represent the data structures that can be used to exchange data between layers.

Obviously, we should focus on the data access layer, DAL for short. The purpose of a DAL is to isolate the data store from other application layers. The components in this layer abstract the semantics of the underlying data access technology, thus allowing the business layer to focus on business logic. Each component typically provides methods to perform CRUD operations for a specific business entity.

Now that we’ve given an overview of a traditional data access layer, it’s time to get back to LINQ and see if LINQ to SQL can be used within a three-tier architecture.

13.2.2. Do we need a separate data access layer or is LINQ to SQL enough?

After reading about LINQ to SQL in chapters 6, 7, and 8, you’re ready to use it in your applications. But the way we’ve used LINQ to SQL so far in this book gives no hint about how to use LINQ to SQL in a multitier architecture. This is because until now we’ve used LINQ to SQL in a RAD[1] way.

1 RAD (rapid application development) is a software development methodology that involves iterative development and the construction of prototypes. Traditionally the RAD approach involves compromises in usability, features, and/or execution speed.

When you develop applications with LINQ to SQL, you can decide to follow one of two directions: Either you use LINQ to SQL transparently all through your application—this is the RAD way we’ve followed until now—or you stick to the traditional three-tier architecture with data access that is built using LINQ to SQL.

With the first option, you get all the benefit of LINQ to SQL. With the second option, you get the benefits of having a clearly defined data access layer. Let’s describe each option separately.

Using LINQ to SQL as your DAL

When LINQ to SQL is used in a RAD way, it acts as a kind of a minimal data-access layer. The classes generated from the LINQ to SQL Designer document (the .dbml file) or the SqlMetal tool are the data entities that form the data object model. There is no data access code in these entities. The SQL code that performs the calls to the database to load or save data from the data entities is generated by a LINQ to SQL DataContext based on queries you write in C# or VB. To actually interact with the database, you need to write LINQ queries that LINQ to SQL will convert into SQL queries.

 

Note

It’s true that by default the data entities generated by the LINQ to SQL designer or the SqlMetal tool are decorated with LINQ to SQL attributes. If you want attribute-free POCOs[2] for your data entity classes, you can use the SqlMetal command-line tool to generate a VB or C# source file and a mapping file, as was described in chapter 7.

2 POCO means Plain-Old CLR Objects. This term is used to contrast a class or an object with one that is designed to be used with a specific framework such as an object-relational mapper.

 

We’ve stated that when you use LINQ without creating a real data access layer, you can get all the benefits of LINQ to SQL. Let’s review these benefits.

It allows us to avoid limitations of the data access layer’s API.

When you’re writing an application, you may often realize that each page requires a custom database query.

For instance, if on one page you need to display the books that have a long title (for some obscure reason we won’t discuss here), then you’ll need a specific method in your data access layer that provides this kind of data. Chances are high that this method won’t be used elsewhere in your application. There is no one-size-fits-all DAL. More precisely, if there is any DAL that has code generic enough to satisfy the needs of every page, it may be at the cost of performance.

If you use LINQ to SQL directly on your page, you can write rich queries that precisely match your needs for that page. Here is an example of such a query:

IEnumerable<Book> booksWithLongTitles =
  from book in dataContext.Books
  where book.Title.Length > 25
  select book;

It can reduce the database workload and network traffic.

When you retrieve data from the database, you can easily select only the fields you need and avoid having too much data loaded for nothing. For example, in the following LINQ to SQL code, only the Title field of the Books table will be retrieved from the database:

IEnumerable<String> longTitles =
  from book in dataContext.Books
  where book.Title.Length > 25
  select book.Title;

If you instead use a standard method proposed by your DAL to retrieve a list of books, several fields can be loaded from the database even if you don’t need them. Typically, such a method would return a full Book object, not just titles. Of course, you can always design your DAL to be able to specify which fields you want to be loaded, but this complicates the code.

More operations can be performed directly by the server.

Often, when you retrieve data from a database, you’d like to perform operations on it so it’s formatted to satisfy your needs. As we’ve just seen, you can define the shape of the data you retrieve. But there are other operations you can do. For example, you can ask the database server to sort, group, or join the data. If you use a generic DAL method, you’re likely to always return data sorted, grouped, or joined in the same way. In contrast, if you use the following query in your presentation layer, the grouping and sorting specified through the orderby and group..by clauses will be performed by the database server and returned to you as you wish them to be:

var longTitles =
  from book in dataContext.Books
  where book.Title.Length > 25
  orderby book.Title
  group book.Title by book.PublisherObject.Name into publisherBooks
  select new { Publisher = publisherBooks.Key,
               BookTitles = publisherBooks };

To sum up the benefits we’ve just listed, we could say that genericity has a cost. What using LINQ to SQL in a RAD way allows is fine-grained customization.

We’ve just identified using LINQ to SQL directly as an interesting solution for data access, but we said previously that we’d consider another option: creating a real data access layer written using LINQ to SQL. Why consider this second option? Are there problems if we use LINQ to SQL directly?

Here are some limitations you should keep in mind if you decide to go the LINQ to SQL RAD route:

  • Writing database queries in your presentation layer is not as bad as using SQL code in it (think about ASP.NET’s SqlDataSource web control for example), but it’s still a questionable practice. If you do it, you’re mixing data access code with business logic and presentation code. This means that you’ve decided to abandon the benefits of the separation of concerns.
  • There is no single place to look when you want to find all that’s related to data access. If all your LINQ to SQL queries are scattered around in your business logic or presentation code, it becomes difficult to review or update all the data access code.
  • Code reuse is not central in such a design. If you want to share LINQ to SQL queries between business objects or screens, where do you put them? You can enrich the DataContext class with predefined queries or validation code, but this makes it look like a big bag of tricks without much structure compared to a real data access layer.
  • If you don’t create a concrete data access layer, where will you put all the data processing that can’t be done with LINQ to SQL? This is close to the previous point. Again you can enrich the DataContext class if this is okay with you.
  • Mapping is limited to the table-per-class model. If you want to have entities that span over several tables, as is often the case, it may be better to either use something other than LINQ to SQL or create an application layer that abstracts this limitation and returns richer entities.

If there were no way to address these concerns then LINQ to SQL would be doomed to be used only in prototypes. If you use LINQ to SQL as is without being careful, it’s easy to unknowingly commit the reprehensible architectural sin of mixing the UI, business logic, and data access layers, for example.

To avoid this kind of quick-and-dirty use, let’s now see how you can create a real data access layer instead of using LINQ to SQL directly in all the layers of your applications.

Using LINQ to SQL to create a real DAL

If you don’t use LINQ to SQL directly in your presentation or business logic layer, you can still use it to create your DAL. This way you can get the benefits of having a true DAL, while still keeping some benefits of LINQ to SQL.

Before going further, it may be good to restate the basic goals for a DAL.[3]

3 Source: Howard Dierking at http://blogs.msdn.com/howard_dierking/archive/2007/04/23/designing-a-domain-driven-data-access-layer.aspx

  • The DAL should completely hide the underlying data storage and the data access technology used, whether it’s an object-relational mapping tool, hand-generated inline SQL, calls to stored procedures, or anything else. This allows the client or upper-level layers to be created with a higher level of abstraction.
  • The DAL should not place any significant constraints on the design of the business object model (also called the domain model).
  • The entire DAL should be replaceable with minimal impact.

All of the aforementioned goals can be summarized with one word: decoupling. When LINQ to SQL is used in a RAD way, this is not really achieved. This is why you may consider creating a real DAL instead of spreading LINQ to SQL queries all over in your applications.

When you create a concrete DAL with LINQ to SQL, there a few points to keep in mind. Here are three of them:

  • The ratio of plumbing code to real code can be high compared to direct LINQ to SQL code.
  • If you return LINQ to SQL entities or queries from your DAL, these objects support deferred execution and lazy fetching, weakening your division.
  • Your DAL should return objects that can be passed between components at different tiers.

We won’t address the first point here. It’s true that the source code of a DAL that uses LINQ to SQL may look useless because it can be simple and could be used directly in other parts of your applications. However, this is what we want to avoid and one reason for creating a DAL, so this is something you should accept. The fact that the code in your DAL is simpler than equivalent code with literal SQL queries does not mean that it’s useless. It’s better, in fact!

Let’s look at the second point. When you write your first DAL method, you may be tempted to write something like listing 13.1.

Listing 13.1. Data access object with a method that returns a query (IQueryable<T>)
public class BookDataAccessObject
{
  LinqBooksDataContext _dataContext = new LinqBooksDataContext();

  public IQueryable<Book> GetBooksBySubjectName(String subjectName)
  {
    return
      from book in _dataContext.Books
      where book.SubjectObject.Name == subjectName
      select book;
  }
}

In the listing, you can see a method named GetBooksBySubjectName that returns an object of type IQueryable<Book>. The result is a simple LINQ to SQL query, hence the result type.

If you create a DAL with LINQ to SQL and have your methods return something like IQueryable<T>, as in our sample, you don’t return data but you do return queries. This makes a big difference compared to a method that would return a collection of Book objects.

In this situation, the calls to the database aren’t performed inside the DAL methods, but outside at a later time. Remember that due to deferred execution, LINQ queries are executed only when they are enumerated.

It may be better to return a list of entities instead of a query. In listing 13.2, the query is executed inside the DAL method, thanks to the call to ToList, and the results are returned in the form of a list of entities.

Listing 13.2. Data access object with a method that returns a collection of objects (List<T>)
public class BookDataAccessObject
{
  LinqBooksDataContext _dataContext = new LinqBooksDataContext();

  public List<Book> GetBooksBySubjectName(String subjectName)
  {
    var query =
      from book in _dataContext.Books
      where book.SubjectObject.Name == subjectName
      select book;
    return query.ToList();
  }
}

If you use lazy loading, more calls to the database happen through transparent calls to LINQ to SQL. For example, by default, if your DAL returns a Subject object and you access its Books property in your business logic or presentation layers, an implicit call to the database is done without your always being aware of it. A solution to avoid this is to return only detached objects (value objects), with lazy loading deactivated.

 

Note

Some would argue that we shouldn’t care when database calls get made, but this is a debate we can’t address in this book.

 

In listing 13.3, Book objects are loaded in advance for each Subject object before the list of subjects is returned. In addition, the DataContext.DeferredLoadingEnabled property is set to false to ensure that no additional calls to the database are made through lazy loading.

Listing 13.3. DAL method returning subjects ordered by name, with lazy loading disabled

Returning value objects is important also when you need to pass objects between remote tiers. This is the third concern we included in our list earlier. If you return a Subject object to a client remote tier, using WCF for example, and this client tier needs data that isn’t loaded with the object, then a call to the database will be attempted if lazy loading is enabled, something that we don’t want and that’s likely to fail anyway.

The problem is that it’s not always easy to know what callers of the DAL methods will need. If your DAL returns a Book object, some callers will access the data about the publishers while others will need to access more information, such as the data about the authors. In any case, it should be made clear which data is loaded by each DAL method, in one way or another.

We’ve just seen that there are a couple of ways you can use LINQ to SQL. One is to use it to replace your DAL; the other is to use it inside your DAL. In your own applications, you’re free to decide what’s better. It depends on whether you want to invest in long-term development or you’re just creating lightweight applications or prototypes.

One of the nice things about LINQ to SQL is that it’s flexible enough to allow you to easily build an object-oriented layer for your data and business logic. Specifically, it provides a way to add validation rules and some business logic in your data entity classes. The entity classes for LINQ to SQL also support persistence ignorance and flexible inheritance (no base class required). In a lot of projects, these features may be used to merge the data and business layers, while in others the traditional separation of concerns will remain the rule.

Before moving on to the other LINQ flavors, let’s see how we use LINQ to SQL in the LinqBooks sample application.

13.2.3. Sample uses of LINQ to SQL in LinqBooks

In the LinqBooks sample, we’ve decided to use LINQ to SQL directly in the presentation layer in most parts of the application to demonstrate how easy it is to write flexible and robust data querying. Still, to demonstrate the second approach we described earlier, we created a sample DAL with a few data access objects. As you’ll see, there are still benefits to using LINQ to SQL inside of your DAL, such as code conciseness, readability, and reliability.

Summary of the options

When LINQ to SQL is used directly in the presentation layer, the architecture of the application is simple. The DataContext class generated using SqlMetal or the LINQ to SQL Designer contains the entity classes that are used directly in the user interface classes. All the data access is performed by the DataContext. If you enrich the DataContext and its entity classes with business logic, the DataContext replaces both the data access layer and the business logic layer.

Figure 13.6 depicts this.

Figure 13.6. An application architecture where the data object model, the data access code and the business logic are all represented by a LINQ to SQL DataContext

When you follow the traditional three-tier architecture, LINQ to SQL is used in a clearly separated data access layer. This is where the DataContext is created and used. In addition, the business logic is coded outside of the DataContext and the data access layer altogether.

Figure 13.7 shows the schema of the complete three-tier architecture, similar to the one we presented in section 13.2.1, but with a LINQ to SQL DataContext as the data access object.

Figure 13.7. A three-tier application architecture, with its presentation, business logic, and LINQ to SQL data access layers

Sample code

Let’s now focus on some code samples to give you an idea of how LINQ to SQL can be used in an application like LinqBooks.

Let’s start with the Publishers.aspx page. Figure 13.8 shows what it looks like.

Figure 13.8. Publishers.aspx page used to display the list of the publishers contained in the LinqBooks database

In Publishers.aspx.cs, a simple LINQ to SQL query is used to retrieve the list of publishers available in the database. See listing 13.4.

Listing 13.4. Retrieving a list of publishers from a database and binding it to a GridView
var query =
  from publisher in _DataContext.Publishers
  orderby publisher.Name
  select publisher;
GridViewPublishers.DataSource = query;
GridViewPublishers.DataBind();

This is a straightforward way of retrieving and displaying data. The GridView that is used to display the data is declared as shown in listing 13.5.

Listing 13.5. ASP.NET markup to display a list of publishers in a GridView
<asp:GridView ID="GridViewPublishers" runat="server"
  AutoGenerateColumns="False"
  OnRowDatabound="GridViewPublishers_RowDataBound">
  <columns>
    <asp:hyperlinkfield DataNavigateUrlFields="ID"
      DataNavigateUrlFormatString="~/Publisher.aspx?ID={0}"
      DataTextField="Name" HeaderText="Name">
    </asp:hyperlinkfield>
    <asp:TemplateField HeaderText="Books">
      <ItemTemplate>
        <linqBooks:BookList ID="BookList" runat="server" />
      </ItemTemplate>
    </asp:TemplateField>
  </columns>
</asp:GridView>

The BookList tag is used to reference a custom control that displays a list of books. The RowDataBound event is used to provide the list of books to display to the BookList user control. This technique is used to ensure compile-time validation of the code. Listing 13.6 shows the code of the event handler for RowDataBound.

Listing 13.6. Handler for the GridView.RowDataBound event used to display a child collection (Publisher.Books)
protected void GridViewPublishers_RowDataBound(object sender,
  GridViewRowEventArgs e)
{
  if (e.Row.DataItem == null)
    return;

  Publisher publisher = (Publisher)e.Row.DataItem;
  BookList bookList = (BookList)e.Row.FindControl("BookList");
  bookList.Books = publisher.Books;
  bookList.DataBind();
}

On this first page, we display the data in a simple grid. If we want to allow the user to sort the data and activate paging, we can use a new component provided by .NET 3.5: LinqDataSource. The LinqDataSource component can work with a DataContext, or you can provide it a LINQ query and it’ll automatically perform paging and sorting operations. This is what we use on the Books.aspx page.

Figure 13.9 shows a screenshot of the Books.aspx page.

Figure 13.9. Books.aspx page used to display the list of the books contained in the LinqBooks database

As you can see, you can click the headers of the columns to sort them, and a pager is available at the bottom of the grid.

A standard GridView control is used to obtain this display, but its DataSource is a LinqDataSource:

<asp:LinqDataSource ID="LinqDataSource1" runat="server"
  OnSelecting="LinqDataSource1_Selecting">
</asp:LinqDataSource>
...
<asp:GridView ID="GridViewBooks" runat="server"
   AllowSorting="True" AllowPaging="True" PageSize="3"
   AutoGenerateColumns="False" DataSourceID="LinqDataSource1">
...

Note that sorting and paging are activated on the GridView control, and PageSize is set to 3.

In Books.aspx.cs, the Selecting event of the LinqDataSource is handled to provide the query used by the DataSource, as shown in listing 13.7.

Listing 13.7. Handler for LinqDataSource.Selecting to provide the query used by the DataSource
protected void LinqDataSource1_Selecting(object sender,
  LinqDataSourceSelectEventArgs e)
{
  e.Result =
    from book in new LinqBooksDataContext().Books
    orderby book.Title
    select new
     {
       Id = book.ID,
       Title = book.Title,
       Publisher = book.PublisherObject.Name,
       Price = book.Price
     };
}

As you can see, the query doesn’t return Book objects but uses anonymous types to return only the data we need. There is no need to load more information from the database than what we really need. Here we display only the titles of the books, the names of their publishers, and their prices, and we use the ID to create and hyperlink to the book details page (Book.aspx).

Listing 13.8 shows the markup for the GridView.

Listing 13.8. Markup to display a list of books in a GridView
<asp:GridView ID="GridViewBooks" runat="server"
   AllowSorting="True" AllowPaging="True" PageSize="3"
   AutoGenerateColumns="False" DataSourceID="LinqDataSource1">
  <Columns>
    <asp:HyperLinkField
      DataNavigateUrlFields="Id"
      DataNavigateUrlFormatString="~/Book.aspx?ID={0}"
      DataTextField="Title" HeaderText="Title"
      SortExpression="Title">
    </asp:HyperLinkField>
    <asp:BoundField DataField="Publisher" HeaderText="Publisher"
      ReadOnly="True" SortExpression="Publisher" />
    <asp:BoundField DataField="Price" HeaderText="Price"
      DataFormatString="{0:F2}" HtmlEncode="false"
      ReadOnly="True" SortExpression="Price" />
  </Columns>
</asp:GridView>

This example shows that a LINQ to SQL query can be used to load only the data needed in a given context. Let’s now take another example: Book.aspx. This page is used to display details about a book. Figure 13.10 shows a sample display.

Figure 13.10. Book.aspx page used to display details about a book

Again, in this page, we use a LINQ to SQL query to select the data we want to display. This example is interesting because it shows how the query and an anonymous type are used to shape the results, with subselections on authors and reviews. See listing 13.9.

Listing 13.9. Using an anonymous type and query operators to shape a list of books for display
var books =
  from book in _DataContext.Books
  where book.ID == _BookId
  select new
    {
      Title = book.Title,
      Isbn = book.Isbn,
      Summary = book.Summary,
      Notes = book.Notes,
      PageCount = book.PageCount,
      Price = book.Price,
      PubDate = book.PubDate,
      PublisherId = book.Publisher,
      PublisherName = book.PublisherObject.Name,
      Authors = book.BookAuthors.Select(
                       bookAuthor => bookAuthor.AuthorObject),
      Subject = book.SubjectObject.Name,
      AverageRating =
        book.Reviews.Average(review => (double?)review.Rating)
    };

We won’t review all the pages of the sample application, but we’d like to point out some specifics so you know what to look at in the source code. Here are some pages you can analyze more precisely:

  • In Author.aspx.cs, you can see how a parameter provided on the query string (in the URL) is used to filter data. See in the Page_Load method how the ID of the author to display is used.
  • In Authors.aspx.cs, you can see how a class named AuthorPresentationModel is used to contain the data we need about an author. As the name indicates, the code of the Authors page demonstrates how to work with a presentation model.[4] This class is then used in the GridViewAuthors_Row DataBound method to work with the retrieved data.

    4 See http://www.martinfowler.com/eaaDev/PresentationModel.html for more information about the Presentation Model design pattern.

  • The btnAddAuthor_Click method in Authors.aspx.cs shows how to add a record into the database.
  • The btnDelete_Click method in Author.aspx.cs demonstrates how to delete a record. The btnDelete_Click method in Book.aspx.cs performs the same kind of operation, but with the additional deletion of linked records.
  • Open the Subjects.aspx.cs file to see how to use a method from the application’s DAL. In the DisplaySubjects method, the DAL is invoked to get the list of all subjects in the database ordered by name, with associated books loaded and lazy loading disabled.

After our focus on LINQ to SQL, it’s time to consider another flavor of LINQ. It was important to address the use of LINQ to SQL in an application, but we should not forget that LINQ is useful for dealing with other kinds of data sources than just relational databases. Let’s now see how we use another major LINQ flavor in Linq-Books: LINQ to XML.

13.3. Use of LINQ to XML

LINQ to XML can be used to read or create XML. Given the wide use of XML nowadays, you can expect to find LINQ to XML employed in every layer of applications. As you saw in chapter 11, where several common LINQ to XML scenarios are covered, it can be used in combination with data from a database, to import XML data, create RSS feeds, and more.

13.3.1. Importing data from Amazon

The first usage of LINQ to XML in LinqBooks comes in the form of a standalone utility.

We covered this scenario, reading XML and updating a database, in chapter 11. In LinqBooks, we reuse the sample Windows Forms application for importing books and details about them from Amazon. See figure 13.11.

Figure 13.11. Windows Forms user interface for importing books from Amazon

This utility allows us to search for books with keywords and select books to import in the LinqBooks database. This sample Windows Forms application demonstrates how to use LINQ to XML to parse the XML data returned by Amazon’s web services. LINQ to SQL is used to insert the imported data into the database.

Here is the LINQ to XML query used to display the list of books:

var books =
  from result in amazonXml.Descendants(ns + "Item")
  let attributes = result.Element(ns + "ItemAttributes")
  select new Book {
     Isbn = (string)attributes.Element(ns + "ISBN"),
     Title = (string)attributes.Element(ns + "Title"),
  };
bookBindingSource.DataSource = books;

The list of books selected using the Import check box is built using LINQ to Objects:

var selectedBooks =
  from row in bookDataGridView.Rows.OfType<DataGridViewRow>()
  where (bool)row.Cells[0].EditedFormattedValue
  select (Book)row.DataBoundItem;

The actual data to import in the database is prepared thanks to LINQ to XML again, as in listing 13.10.

Listing 13.10. LINQ to XML query used to prepare data to be inserted into a database
var booksToImport =
  from amazonItem in amazonXml.Descendants(ns + "Item")
  join selectedBook in selectedBooks
    on (string)amazonItem
                  .Element(ns + "ItemAttributes")
                  .Element(ns + "ISBN")
    equals selectedBook.Isbn
  join p in ctx.Publishers
    on (string)amazonItem
                  .Element(ns + "ItemAttributes")
                  .Element(ns + "Publisher")
    equals p.Name into publishers
  from existingPublisher in publishers.DefaultIfEmpty()
  let attributes = amazonItem.Element(ns + "ItemAttributes")
  select new Book {
    ID = Guid.NewGuid(),
    Isbn = (string)attributes.Element(ns + "ISBN"),
    Title = (string)attributes.Element(ns + "Title"),
    Publisher = (existingPublisher ??
               new Publisher {
                 ID = Guid.NewGuid(),
                 Name = (string)attributes.Element(ns + "Publisher")
               }
    ),
    Subject = (Subject)categoryComboBox.SelectedItem,
    PubDate = (DateTime)attributes.Element(ns + "PublicationDate"),
    Price = ParsePrice(attributes.Element(ns + "ListPrice")),
    BookAuthors = GetAuthors(attributes.Elements(ns + "Author"))
  };

See chapter 11 for complete details about this utility and this kind of use of LINQ to XML.

Let’s now look at another use of LINQ to XML, relying on its capability to generate XML documents.

13.3.2. Generating RSS feeds

LINQ to XML can be used to generate XML documents, such as RSS feeds. This scenario was covered in chapter 11. In LinqBooks, we also create RSS feeds, based on data coming from a database through LINQ to SQL.

A sample RSS feed published by the LinqBooks web site returns the list of reviews contained in the database. This RSS feed is generated and returned by a web method as an XmlDocument. Listing 13.11 shows the complete code of the web method.

Listing 13.11. Web method that creates an RSS feed and returns it as an XmlDocument (RSS.asmx.cs)
[WebMethod]
public XmlDocument GetReviews()
{
  var dataContext = new LinqBooksDataContext();

  var xml =
    new XElement("rss",
      new XAttribute("version", "2.0"),
      new XElement("channel",
        new XElement("title", "LinqBooks reviews"),
        from review in dataContext.Reviews
        select new XElement("item",
          new XElement("title",
            "Review of ""+review.BookObject.Title+"" by "+
            review.UserObject.Name),
          new XElement("description", review.Comments),
          new XElement("link",
            "http://example.com/Book.aspx?ID="+
            review.BookObject.ID.ToString())
        )
      )
    );

  XmlDocument result = new XmlDocument();
  result.Load(xml.CreateReader());
  return result;
}

You can see how LINQ to XML streamlines the creation of simple XML documents such as RSS feeds. This is why you’re likely to use LINQ to XML everywhere XML is required.

We’d demonstrate other uses of LINQ to XML, but we have other LINQ flavors to cover. Let’s see how LINQ to DataSet can be useful in an application like LinqBooks.

13.4. Use of LINQ to DataSet

One feature of the LinqBooks application is to let you export the complete set of data contained in your book catalog as an XML document. This can be useful to create backups of your data. It can also be used to share data with your friends. To that effect, we also implemented an import feature in the application.

These import and export features are implemented through the use of DataSets. For simplicity, the export is performed using the TableAdapters generated with the typed DataSet class, so LINQ is not used in this operation. The source code, shown in listing 13.12, is pretty straightforward.

Listing 13.12. Loading the complete data from the database into a typed DataSet (GetXML.ashx.cs)
LinqBooksDataSet dataSet = new LinqBooksDataSet();
new SubjectTableAdapter().Fill(dataSet.Subject);
new PublisherTableAdapter().Fill(dataSet.Publisher);
new BookTableAdapter().Fill(dataSet.Book);
new AuthorTableAdapter().Fill(dataSet.Author);
new BookAuthorTableAdapter().Fill(dataSet.BookAuthor);
new UserTableAdapter().Fill(dataSet.User);
new ReviewTableAdapter().Fill(dataSet.Review);

The import operation is more advanced. The goal is to allow you to load an existing XML document, provided by a friend for example, and select which books to import into your catalog. In the implementation, we use LINQ to DataSet to allow this.

 

Note

To discover LINQ to DataSet, please read our online chapter, which is available on the web. See http://LinqInAction.net.

 

Figure 13.12 shows what the web page looks like once an XML document has been uploaded.

Figure 13.12. The XML import/export page displaying data from an uploaded XML document

The first step during an import operation is to load the selected XML document into a DataSet. Here is how it’s done:

var dataSet = new LinqBooksDataSet();
dataSet.ReadXml(uploadXml.FileContent);
Session["DataSet"] = dataSet;

Note that we store the DataSet in the ASP.NET session so it’s easily available.

Once the data is loaded, we can query the DataSet to display the list of the books that are already in your catalog. First, we need to prepare a list of the books in your database:

var dataContext = new LinqBooksDataContext();
IEnumerable<String> knownTitles =
  dataContext.Books.Select(book => book.Title);

Then, we can use this list to filter the list of books that are in the DataSet, as in listing 13.13.

Listing 13.13. Filtering and displaying data from a DataSet (XMLImportExport.aspx.cs)
var dataSet = (LinqBooksDataSet)Session["DataSet"];
var queryExisting =
  from book in dataSet.Book
  where knownTitles.Contains(book.Title)
  orderby book.Title
  select new {
           Title = book.Title,
           Publisher = book.PublisherRow.Name,
           ISBN = book.Field<String>("Isbn"),
           Subject = book.SubjectRow.Name
         };
GridViewDataSetExisting.DataSource = queryExisting;
GridViewDataSetExisting.DataBind();

We can also display the list of the books that are not yet in your catalog, which is a similar operation, except that the condition used for filtering is reversed. Listing 13.14 shows the source code.

Listing 13.14. Filtering and displaying data from a DataSet (XMLImportExport.aspx.cs)
var queryNew =
  from book in dataSet.Book
  where !knownTitles.Contains(book.Title)
  orderby book.Title
  select new {
           Id = book.ID,
           Title = book.Title,
           Publisher = book.PublisherRow.Name,
           ISBN = book.Field<String>("Isbn"),
           Subject = book.SubjectRow.Name
         };
GridViewDataSetNew.DataSource = queryNew;
GridViewDataSetNew.DataBind();

Finally, the books you select are imported using a mix of DataSet and LINQ to SQL queries, as shown in listing 13.15.

Listing 13.15. Inserting books from a DataSet into a database (XMLImportExport.aspx.cs)

Some developers depict DataSets as outdated or harmful and advise against using them, but they’re a useful tool for features like the one we’ve just presented. As we demonstrate in our online chapter, LINQ to DataSet makes it easier to query DataSets. It’s one more companion tool on your belt wherever you need to deal with DataSets.

13.5. Using LINQ to Objects

We don’t really use LINQ to Objects separately in the LinqBooks application. It’s used in combination with LINQ to XML or LINQ to SQL. However, in some places, we use small LINQ to Objects queries to simplify some code. Here is an example you already saw in the section Importing data from Amazon:

var selectedBooks =
  from row in bookDataGridView.Rows.OfType<DataGridViewRow>()
  where (bool)row.Cells[0].EditedFormattedValue
  select (Book)row.DataBoundItem;

Depending on the type of your applications, you may find LINQ to Objects useful by itself. In fact, as soon as you need to query collections, such as arrays, lists or dictionaries, you’re likely to find LINQ to Objects a useful tool. The fact that it works with in-memory collections makes it suitable for any layer of your applications. We already demonstrated this extensively in other chapters, especially in part 1. This is why there’s no reason to spend more time on LINQ to Objects at this point.

13.6. Extensibility

In chapter 12, we covered LINQ’s extensibility and provided sample custom query operators. In the same chapter, we also introduced a new LINQ flavor named LINQ to Amazon. In LinqBooks, we reuse some of the custom query operators from previous chapters. We also use LINQ to Amazon to provide a light data import facility.

13.6.1. Custom query operators

The first custom query operator that is used in LinqBooks is TotalPrice. We created this query operator in chapter 12. It iterates over an enumeration of books and returns the sum of the books’ prices. This operator demonstrates how you can create custom query operators to simplify your code. For example, once you’ve created the TotalPrice operator, getting the total price of all books in your LinqBooks catalog can be achieved with the following simple code:

var dataContext = new LinqBooksDataContext();
lblTotalPrice.Text = dataContext.Books.TotalPrice().ToString("F2");

Another custom query operator used in LinqBooks comes from chapter 5: MaxElement. The goal of this operator is to retrieve an object from a collection.

Book biggestBook =
  dataContext.Books.Where(book => book.Title.Length > 0)
                   .MaxElement(book => book.PageCount);
lnkBiggestBook.Text = biggestBook.Title;
lnkBiggestBook.NavigateUrl = "~/Book.aspx?ID=" + biggestBook.ID;
lblPageCount.Text = biggestBook.PageCount.ToString();

 

Note

The queries in this section retrieve all the data from the database before working in memory.

The uniform syntax between the various LINQ flavors allows us to mix them together in queries. In these examples, LINQ to SQL and LINQ to Objects codes are combined in each query. This is elegant and useful, but you should be aware that the query executes in two stages. Each flavor executes separately. For example, in the case of the following query, a SQL query is first executed to fetch books that match the condition in Where, and then the MaxElement operator is executed in memory over the results:

 

Sample uses of both TotalPrice and MaxElement are provided in the Books. aspx.cs file.

13.6.2. Creating and using a custom LINQ provider

Our LinqBooks sample application offers the ability to import books from Amazon through a Windows Forms application. In order to show how a custom LINQ provider can be useful in a real-life application, we’ve added another import facility on the Add Books page. This time, we reuse the AmazonBookSearch class from LINQ to Amazon, which we introduced in the previous chapter.

Listing 13.16 shows the LINQ to Amazon query that is used.

Listing 13.16. Querying Amazon using LINQ to Amazon with dynamic criteria (AddBooks.aspx.cs)
var query =
  from book in new AmazonBookSearch()
  where
    book.Title.Contains(txtSearchKeywords.Text) &&
  (book.Publisher == txtSearchPublisher.Text) &&
  (book.Price <= 25) &&
  (book.Condition == BookCondition.New)
orderby book.Title
select book;

The query is used directly to display the results:

GridViewAmazonBooks.DataSource = query;
GridViewAmazonBooks.DataBind();

A custom LINQ implementation such as LINQ to Amazon allows us to write simple and declarative code. Here we don’t need to worry about how the call to Amazon is made and how to retrieve and parse the data it returns.

If you need to deal with a service or an API in your application, inquire whether a LINQ version is available. It can simplify your work. If no LINQ API is available, you may consider creating one by yourself. Be warned that this can be a difficult enterprise if you don’t fully master the implementation details of a LINQ provider.

13.7. A look into the future

In this book, we presented LINQ as it stands today. A good thing about LINQ is that it’s extensible and can easily evolve to support new scenarios and new data sources.

Custom LINQ flavors have already started to appear, and we’ll give you a quick list of them now. We’ll also spend some time describing what Microsoft has announced for the future of LINQ.

13.7.1. Custom LINQ flavors

Additional LINQ implementations have started to appear. Most of them don’t come from Microsoft. They’re possible thanks to the extensibility features built into LINQ.

Here are LINQ flavors:

Here are products that offer support for LINQ:

As far as Microsoft is concerned, the future of LINQ consists at least of LINQ to XSD, PLINQ, and LINQ to Entities. Let’s review them quickly one by one.

13.7.2. LINQ to XSD, the typed LINQ to XML

LINQ to XSD, which is available at the time of this writing only as an alpha version, is designed to allow strongly typed XML queries. It provides developers with support for typed XML programming on top of LINQ to XML. While LINQ to XML programmers operate on generic XML trees, LINQ to XSD programmers operate on typed XML trees. A typed XML tree consists of instances of .NET types that model the XML types of a specific XML schema (XSD).

A LINQ query is worth a thousand words, so let’s compare a LINQ to XML query to a LINQ to XSD query, and you’ll quickly understand the difference between the two technologies.

Consider the following C# fragment for a LINQ to XML query that computes the total over the items in a XML tree for a purchase order:

from item in purchaseOrder.Elements("Item")
select (double)item.Element("Price") * (int)item.Element("Quantity")

Using LINQ to XSD, the same query is written in a much clearer and type-safe way:

from item in purchaseOrder.Item
select item.Price * item.Quantity

As you can see, there’s no need for the dangerous strings and type casting with LINQ to XSD. Everything is strongly typed and structured.

Unfortunately, no release data has been announced for LINQ to XSD, and it has not been updated to the RTM of .NET 3.5. Will Microsoft pursue the work on this approach?

13.7.3. PLINQ: LINQ meets parallel computing

PLINQ, as we told you in chapter 2, means Parallel LINQ. It’s a key component of Parallel FX(PFX), the next generation of concurrency support in the .NET Framework. The goal is to take advantage of LINQ queries to distribute processing over multiple CPUs or cores. The idea is that you can write LINQ queries in the same way you do today, but they get split up and run in parallel. The advantage is that with PLINQ, LINQ queries become a source of performance gains.

The key element in PLINQ is the AsParallel query operator. It integrates with your LINQ queries to have them run in parallel:

IEnumerable<T> leftData = ..., rightData = ...;
var query =
  from x in leftData.AsParallel()
  join y in rightData on x.A == y.B
  select f(x, y);

A first preview of PLINQ was released November 28, 2007. Microsoft hasn’t further revealed plans in terms of release schedule.

An overview of PLINQ was published in MSDN Magazine in October 2007 (“Running queries on multi-core processors” at http://msdn.microsoft.com/msdnmag/issues/07/10/PLINQ/).

Another project from Microsoft related to distributed computing is DryadLINQ. DryadLINQ is a research project that combines the Dryad distributed execution engine and LINQ. Dryad enables reliable, distributed computing on thousands of servers for large-scale data parallel applications. You can learn more about Dryad and DryadLINQ at http://research.microsoft.com/research/sv/DryadLINQ/.

13.7.4. LINQ to Entities, a LINQ interface for the ADO.NET Entity Framework

We already wrote briefly about LINQ to Entities at the end of chapter 8 when we presented the ADO.NET Entity Framework. Like LINQ to SQL, the Entity Framework and LINQ to Entities can be used to perform object-relational mapping. Unlike LINQ to SQL, the Entity Framework will support more database engines than just SQL Server. The fact that LINQ to SQL works only with SQL Server is a big limitation. Will more database engines be supported? When? Microsoft has not announced anything about this lately.

Also, the Entity Framework allows a richer mapping. It works with a true abstraction layer between the application and the database. While LINQ to SQL supports only a direct one-to-one mapping between classes and tables, the Entity Framework allows creating higher-level entity models.

Several previews of the ADO.NET Entity Framework have been made available, but no precise date has been announced for the final release. Microsoft declared that it has targeted the first half of 2008 to ship the ADO.NET Entity Framework as an update to the .NET Framework 3.5 and to Visual Studio 2008.

13.8. Summary

We hope you’ve found everything you needed to get started with LINQ. You can now use it as a powerful tool to write your own production applications. We’ve told you a lot in this book, but because LINQ is a rich subject, we’re sure you’ll still discover a lot about it as you use it.

Happy LINQing!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.136.84