Evjen c09.tex V2 - 01/28/2008 2:09pm Page 476 Chapter 9: Querying with LINQ Select New With {m.Title, .Genre = g.Name}).Skip(10).Take(10) Me.GridView1.DataSource = query Me.GridView1.DataBind() End Sub C# protected void Page_Load(object sender, EventArgs e) { var movies = GetMovies(); var genres = GetGenres(); var query = (from m in movies join g in genres on m.Genre equals g.ID select new { m.Title, g.Name }).Skip(10).Take(10); this.GridView1.DataSource = query; this.GridView1.DataBind(); } When running this code, you will see that the results start with the tenth record in the list, and only ten records are displayed. LINQ to XML The second flavor of LINQ is called LINQ to XML (or XLINQ). As the name implies, LINQ to XML enables you to use the same basic LINQ syntax to query XML documents. As with the basic LINQ fea- tures, the LINQ to XML features of .NET are included as an extension to the basic .NET framework, and do not change any existing functionality. Also, as with the core LINQ features, the LINQ to XML features are contained in their own separate assembly, the System.Xml.Linq assembly. In this section, to show how you can use LINQ to query XML, we use the same basic Movie data as in the previous section, but converted to XML. Listing 9-18 shows a portion of the Movie data converted to a simple XML document. The XML file containing the complete set of converted data can be found in the downloadable code for this chapter. Listing 9 -18: Sample Movies XML data file <?xml version="1.0" encoding="utf-8" ?> <Movies> <Movie> <Title>Shrek</Title> <Director>Andrew Adamson</Director> <Genre>0</Genre> <ReleaseDate>5/16/2001</ReleaseDate> <RunTime>89</RunTime> </Movie> <Movie> <Title>Fletch</Title> <Director>Michael Ritchie</Director> <Genre>0</Genre> <ReleaseDate>5/31/1985</ReleaseDate> 476 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 477 Chapter 9: Querying with LINQ <RunTime>96</RunTime> </Movie> <Movie> <Title>Casablanca</Title> <Director>Michael Curtiz</Director> <Genre>1</Genre> <ReleaseDate>1/1/1942</ReleaseDate> <RunTime>102</RunTime> </Movie> </Movies> To get started seeing how you can use LINQ to XML to query XML documents, let’s walk through some of the same basic queries we started with in the previous section. Listing 9-19 demonstrates a simple selection query using LINQ to XML. Listing 9 -19: Querying the XML Data file using LINQ VB <%@ Page Language="VB" %> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim query = From m In XElement.Load(MapPath("Movies.xml")).Elements("Movie") _ Select m Me.GridView1.DataSource = query Me.GridView1.DataBind() End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>My Favorite Movies</title> </head> <body> <form id="form1" runat="server"> <div> <asp:GridView ID="GridView1" runat="server"> </asp:GridView> </div> </form> </body> </html> C# <script runat="server"> protected void Page_Load(object sender, EventArgs e) { var query = from m in XElement.Load(MapPath("Movies.xml")).Elements("Movie") select m; this.GridView1.DataSource = query; this.GridView1.DataBind(); } </script> 477 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 478 Chapter 9: Querying with LINQ Notice that in this query, you tell LINQ directly where to load the XML data from, and from which elements in that document it should retrieve the data, which in this case are all of the Movie elements. Other than that minor change, the LINQ query is identical to queries we have seen previously. When you execute this code, you get a page that looks like Figure 9-6. Figure 9-6 LINQ to XML raw query results Notice that the fields included in the resultset of the query don’t really show the node data as you might have expected, with each child node as a separate Field in the GridView. This is because the query used in the Listing returns a collection of generic XElement objects, not Movie objects as you might have expected. This is because by itself, LINQ has no way of identifying what object type each node should be mapped to. Thankfully, you can add a bit of mapping logic to the query to tell it to map each node to a Movie object, and how the nodes sub-elements should map to the properties of the Movie object. This is shown in Listing 9-20. Listing 9-20: Mapping XML elements using LINQ VB Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim query = From m In XElement.Load(MapPath("Movies.xml")).Elements("Movie") _ Select New Movie With { _ .Title = CStr(m.Element("Title")), _ .Director = CStr(m.Element("Director")), _ .Genre = CInt(m.Element("Genre")), _ .ReleaseDate = CDate(m.Element ("ReleaseDate")), _ .Runtime = CInt(m.Element("Runtime")) _ } Me.GridView1.DataSource = query Me.GridView1.DataBind() End Sub C# protected void Page_Load(object sender, EventArgs e) 478 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 479 Chapter 9: Querying with LINQ { var query = from m in XElement.Load(MapPath("Movies.xml")).Elements("Movie") select new Movie { Title = (string)m.Element("Title"), Director = (string)m.Element("Director"), Genre = (int)m.Element("Genre"), ReleaseDate = (DateTime)m.Element("ReleaseDate"), RunTime = (int)m.Element("RunTime") }; this.GridView1.DataSource = query; this.GridView1.DataBind(); } As you can see, we have modified the query to include mapping logic so that LINQ knows what our actual intentions are — to create a resultset that contains the values of the Movie elements inner nodes. Running this code now results in a GridView that contains what we want, as shown in Figure 9-7. Figure 9-7 LINQ to XML query with the data properly mapped to a Movie object Note that the XElements Load method attempts to load the entire XML document; therefore, it is not a good idea to try to load very large XML files using this method. Joining XML Data LINQ to XML supports all of the same query filtering and grouping operations as LINQ to Objects. It also supports joining data, and can actually union together data from two different XML documents — a task that previously would have been quite difficult. Let’s look at the same basic join scenario as was presented in the LINQ to objects section. Again, our basic XML data includes only an ID value for the Genre. It would, however, be better to show the actual Genre name with our resultset. 479 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 480 Chapter 9: Querying with LINQ In the case of the XML data, rather than being kept in a separate List, the Genre data is actually stored in a completed separate XML file, showing in Listing 9-21. Listing 9-21: Genres XML data <?xml version="1.0" encoding="utf-8" ?> <Genres> <Genre> <ID>0</ID> <Name>Comedy</Name> </Genre> <Genre> <ID>1</ID> <Name>Drama</Name> </Genre> <Genre> <ID>2</ID> <Name>Action</Name> </Genre> </Genres> To join the data together, you can use a very similar join query to that used in Listing 9-16. This is shown in Listing 9-22. Listing 9 -22: Joining XML data using LINQ VB Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim query = From m In XElement.Load(MapPath("Listing9-18.xml")) .Elements("Movie") _ Join g In XElement.Load(MapPath("Listing9-21.xml")).Elements ("Genre") _ On CInt(m.Element("Genre")) Equals CInt(g.Element("ID")) _ Select New With { _ .Title = CStr(m.Element("Title")), _ .Director = CStr(m.Element("Director")), _ .Genre = CStr(g.Element("Name")), _ .ReleaseDate = CDate(m.Element("ReleaseDate")), _ .Runtime = CInt(m.Element("RunTime")) _ } Me.GridView1.DataSource = query Me.GridView1.DataBind() End Sub C# protected void Page_Load(object sender, EventArgs e) { var query = from m in XElement.Load(MapPath("Movies.xml")).Elements("Movie") join g in XElement.Load(MapPath("Genres.xml")).Elements("Genre") on (int)m.Element("Genre") equals (int)g.Element("ID") select new { 480 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 481 Chapter 9: Querying with LINQ Title = (string)m.Element("Title"), Director = (string)m.Element("Director"), Genre = (string)g.Element("Name"), ReleaseDate = (DateTime)m.Element("ReleaseDate"), RunTime = (int)m.Element("RunTime") }; this.GridView1.DataSource = query; this.GridView1.DataBind(); } In this sample, you can see we are using the XElement.Load method as part of the LINQ join statement to tell LINQ where to load the Genre data from. Once the data is joined, you can access the elements of the Genre data as you can the elements of the movie data. LINQ to SQL LINQ to SQL is the last form of LINQ in this release of .NET. LINQ to SQL, as the name implies, enables you to quickly and easily query SQL-based data sources, such as SQL Server 2005. As with the prior flavors of LINQ, LINQ to SQL is an extension of .NET. Its features are located in the System.Data.Linq assembly. In addition to the normal Intellisense and strong type checking that every flavor of LINQ gives you, LINQ to SQL also includes a basic Object Relation (O/R) mapper directly in Visual Studio. The O/R mapper enables you to quickly map SQL-based data sources to CLR objects that you can then use LINQ to query. It is the easiest way to get started using LINQ to SQL. The O/R mapper is used by adding the new Linq to SQL Classes file to your Web site project. The Linq to SQL File document type allows you to easily and visually create data contexts that you can then access and query with LINQ queries. Figure 9-8 shows the Linq to SQL Classes file type in the Add New Item dialog. After clicking the Add New Items dialog’s OK button to add the file to your project, Visual Studio notifies you that it wants to add the LINQ to SQL File to your Web site’s App_Code directory. By locating the file there, the data context created by the LINQ to SQL Classes file will be accessible from anywhere in your Web site. Once the file has been added, Visual Studio automatically opens it in the LINQ to SQL design surface. This is a simple Object Relation mapper design tool, enabling you to add, create, remove, and relate data objects. As you modify objects to the design surface, LINQ to SQL is generating object classes that mirror the structure of each of those objects. Later when you are ready to begin writing LINQ queries against the data objects, these classes will allow Visual Studio to provide you with design-time Intellisense support, strong typing and compile-time type checking. Because the O/R mapper is primarily designed to be used with LINQ to SQL, it also makes it easy to create CLR object representations of SQL objects, such as Tables, Views, and Stored Procedures. To demonstrate using LINQ to SQL, we will use the same sample Movie data used in previous sections of this chapter. For this section, the data is stored in a SQL Server Express database. A c opy of this database is included in the downloadable code from the Wrox Web site ( www.wrox.com ). 481 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 482 Chapter 9: Querying with LINQ After the design surface is open and ready, open the Visual Studio Server Explorer tool and locate the Movies database and expand the database’s Tables folder. Drag the Movies table from the Server Explorer onto the design surface. Notice that as soon as you drop the database table onto the design surface, it is automatically interrogated to identify its structure. A corresponding entity class is created by the designer and shown on the design surface. Figure 9-8 The Add New Item dialog includes the new Linq to SQL File type When you drop table objects onto the LINQ to SQL design surface, Visual Studio examines the entities name and will if necessary, attempt to automatically pluralize the class names it generated. It does this in order to help you more closely following the .NET Framework class naming standards. For example, if you drop the Products table from the Northwind database onto the design surface, it would automatically choose the singular name Product as the name of the generated class. Unfortunately, while the designer generally does a pretty good job at figuring out the correct pluraliza- tion for the class names, it’s not 100% accurate. Case in point, simply look at how it incorrectly pluralizes the Movies table to Movy when you drop it into the design surface. Thankfully the designer also allows you to change the name of entities on the design surface. You can do this simply by selecting the entity on the design surface and clicking on the entities name in designer. Once you have added the Movie entity, drag the Genres table onto the design surface. Again, Visual Studio creates a class representation of this table (and notice it gives it the singular name Genre). Addi- tionally, it detects an existing foreign key relationship between the Movie and Genre. Because it detects this relationship, a dashed line is added between the two tables. The lines arrow indicates the direction 482 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 483 Chapter 9: Querying with LINQ of the foreign key relationship that exists between the two tables. The LINQ to SQL design surface with Movies and Genres tables added is shown in Figure 9-9. Figure 9-9 The Movies and Genres tables after being added to the LINQ to SQL design surface Now that you have set up your LINQ to SQL File, accessing its data context and querying its data is simple. To start, you need to create an instance of the data context in the Web page where you will be accessing the data, as shown in Listing 9-23. Listing 9 -23: Creating a new Data Context VB <%@ Page Language="VB" %> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim dc As New MoviesDataContext() End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml"> Continued 483 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 484 Chapter 9: Querying with LINQ <head runat="server"> <title> My Favorite Movies </title> </head> <body> <form id="form1" runat="server"> <div> <asp:GridView ID="GridView1" runat="server"> </asp:GridView> </div> </form> </body> </html> C# <script runat="server"> protected void Page_Load(object sender, EventArgs e) { MoviesDataContext dc = new MoviesDataContext(); } </script> In this case you created an instance of the MoviesDataContext, which is the name of the data context class generated by the LINQ to SQL File you added earlier. Because the data context class is automatically generated by the LINQ to SQL file, its name will change each time you create a new LINQ to SQL file. The name of this class is determined by appending the name of your LINQ to SQL Class file with the DataContext suffix, so had you named your LINQ to SQL File Northwind.dbml, the data context class would have been NorthwindDataContext. After you have added the data context to your page, you can begin writing LINQ queries against it. As mentioned earlier, because LINQ to SQL generated object classes mirror the structure of our database tables, you will get Intellisense support as you write your LINQ queries. Listing 9-24 shows the same basic Movie listing query that has been shown in prior sections. Listing 9 -24: Querying Movie data from LINQ to SQL VB Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim dc As New MoviesDataContext() Dim query = From m In dc.Movies _ Select m Me.GridView1.DataSource = query Me.GridView1.DataBind() End Sub C# protected void Page_Load(object sender, EventArgs e) { MoviesDataContext dc = new MoviesDataContext(); 484 Evjen c09.tex V2 - 01/28/2008 2:09pm Page 485 Chapter 9: Querying with LINQ var query = from m in dc.Movies select m; this.GridView1.DataSource = query; this.GridView1.DataBind(); } As is shown in Figure 9-10, running the code generates a raw list of the Movies in our database. Figure 9-10 Data retrieved from a basic LINQ to SQL query Note that we did not have to write any of the database access code that would typically have been required to create this page. LINQ has taken care of that for us, even generating the SQL query based on our LINQ syntax. You can see the SQL that LINQ generated for the query by writing the query to the Visual Studio output window, as shown in Listing 9-25. Listing 9-25: Writing the LINQ to SQL query to the output window VB Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim dc As New MoviesDataContext() Dim query = From m In dc.Movies _ Select m System.Diagnostics.Debug.WriteLine(query) Me.GridView1.DataSource = query Continued 485 . large XML files using this method. Joining XML Data LINQ to XML supports all of the same query filtering and grouping operations as LINQ to Objects. It also supports joining data, and can actually. ?> <Genres> <Genre> <ID>0</ID> <Name>Comedy</Name> </Genre> <Genre> <ID>1</ID> <Name>Drama</Name> </Genre> <Genre> <ID>2</ID> <Name>Action</Name> </Genre> </Genres> To join the data together, you can use a very similar join query to that used in Listing 9-16. This is shown in Listing 9-22. Listing 9 -22: Joining XML data using LINQ VB Protected. Querying with LINQ In the case of the XML data, rather than being kept in a separate List, the Genre data is actually stored in a completed separate XML file, showing in Listing 9-21. Listing 9-21: