This chapter covers:
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.
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.
Here are the main features of the LinqBooks application:
The technical features implemented include
Let’s now get an idea of the UI that exposes these features.
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.
The page that displays the details for a book can be seen in figure 13.2.
Figure 13.3 shows the page that displays the list of publishers.
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.
The LinqBooks application relies on a SQL Server database. The database schema we use is shown in figure 13.4.
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.
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.
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:
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.
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 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.
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.
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.
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:
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.
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
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
<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.
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.
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.
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.
<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.
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.
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:
4 See http://www.martinfowler.com/eaaDev/PresentationModel.html for more information about the Presentation Model design pattern.
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.
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.
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.
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.
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.
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.
[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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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();
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.
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.
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.
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.
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.
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?
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/.
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.
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!
18.191.233.205