ptg 964 CHAPTER 20 Data Access with LINQ to SQL Performing Standard Database Commands with LINQ to SQL In this section, you learn how to use LINQ to SQL as a replacement for working directly with SQL. We start by discussing how LINQ to SQL queries differ from standard LINQ queries. Next, we examine how you can perform standard database queries and commands using LINQ to SQL such as Select, Update, Insert, and Delete commands. We’ll also discuss how you can create dynamic queries with LINQ. Finally, we investigate the important topic of how you can debug LINQ to SQL queries. LINQ to Objects Versus LINQ to SQL You can use standard LINQ (LINQ to Objects) with any object that implements the IEnumerable<T> interface interface>. You can use LINQ to SQL, on the other hand, with any object that implements the IQueryable<T> interface. Standard LINQ is implemented with the extension methods exposed by the System.Linq.Enumerable class. LINQ to SQL, on the other hand, uses the extension methods exposed by the System.Linq.Queryable class. Why the difference? When you build a query using standard LINQ, the query executes immediately. When you build a query using LINQ to SQL, on the hand, the query does not execute until you start enumerating the results. In other words, the query doesn’t execute until you use a foreach loop to walk through the query results. Consider the following valid LINQ to SQL query: var query = tMovie.Where(m => m.Director == “Steven Spielberg”) .OrderBy( m => m.BoxOfficeTotals ) .Select( m => m.Title ); This query returns a list of movies directed by Steven Spielberg in order of the movie box office totals. You want LINQ to SQL to execute this query against the database in the most efficient way possible. In particular, you don’t want LINQ to SQL to execute each method independently; you want LINQ to SQL to execute one smart database query. When executing this query, it would be bad if LINQ to SQL (1) grabbed all the Movie records that were directed by Steven Spielberg; (2) sorted the records; and then (3) discarded all the columns except the Title column. You want LINQ to SQL to perform one smart database query that looks like this: SELECT [t0].[Title] FROM [Movie] AS [t0] WHERE [t0].[Director] = @p0 ORDER BY [t0].[BoxOfficeTotals] This SQL query is the exact query that LINQ to SQL performs. LINQ to SQL defers execu- tion of a query until you start iterating through the results of the query. When you build a query, you are in reality building a representation of the query. Technically, you are build- ing an expression tree. That way, LINQ to SQL can translate the query into one efficient SQL statement when it comes time to actually execute it. From the Library of Wow! eBook ptg 965 Performing Standard Database Commands with LINQ to SQL To summarize, when you build a query using standard LINQ, the query executes as you build it. When you build a query using LINQ to SQL, you are building a representation of a query that doesn’t actually execute until you start iterating through the query’s results. NOTE When people first start using LINQ, they always worry about how they can build the equivalent of dynamic SQL commands. Later in this section, you learn how to create dynamic LINQ to SQL queries by dynamically building expression trees. Selecting with LINQ to SQL If you want to perform a simple, unordered select, you can use the following query (assuming that you have an entity named Movie that represents the Movies database table): MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies; No LINQ extension methods are used in this query. All the items are retrieved from the Movies table. If you prefer, you can use query syntax instead of method syntax, like this: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = from m in db.Movies select m; Selecting Particular Columns If you want to select only particular columns, and not all the columns, from a database table, you can create an anonymous type on-the-fly, like this: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies.Select( m => new {m.Id, m.Title} ); The expression new {m.Id, m.Title} creates an anonymous type that has two properties: Id and Title. The names of the properties of the anonymous type are inferred. If you want to be more explicit, or if you want to change the names of the anonymous type’s properties, you can construct your query like this: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies.Select( m => new {Id = m.Id, MovieTitle = m.Title} ); Selecting Particular Rows If you want to select only particular rows from a database table and not all the rows, you can take advantage of the Where() method. The following LINQ to SQL query retrieves all the movies directed by George Lucas with box office totals greater than $100,000: 20 From the Library of Wow! eBook ptg 966 CHAPTER 20 Data Access with LINQ to SQL MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies .Where( m => m.Director == “George Lucas” && m.BoxOfficeTotals > 100000.00m) .Select( m => new {m.Title, m.Director, m.BoxOfficeTotals}); Remember to always call the Where() method before the Select() method. You need to filter your data with Where() before you shape it with Select(). Selecting Rows in a Particular Order You can use the following methods to control the order in which rows are returned from a LINQ to SQL query: . OrderBy()—Returns query results in a particular ascending order. . OrderByDescending()—Returns query results in a particular descending order. . ThenBy()—Returns query results using in an additional ascending order. . ThenByDescending()—Returns query results using an additional descending order. The OrderBy() and OrderBy() methods return an IOrderedQueryable<T> collection instead of the normal IQueryable<T> collection type collection type>. If you want to perform additional sorting, you need to call either the ThenBy() or ThenByDescending() method. The following query returns movies in order of release date and then in order of box office totals: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies.OrderBy(m=>m.DateReleased).ThenBy(m=>m.BoxOfficeTotals); Executing this LINQ to SQL query executes the following SQL query: SELECT [t0].[Id], [t0].[CategoryId], [t0].[Title], [t0].[Director], [t0].[DateReleased], [t0].[InTheaters], [t0].[BoxOfficeTotals], [t0].[Description] FROM [dbo].[Movie] AS [t0] ORDER BY [t0].[DateReleased], [t0].[BoxOfficeTotals] Selecting a Single Row If you want to select a single row from the database, you can use one of the following two query methods: From the Library of Wow! eBook ptg 967 Performing Standard Database Commands with LINQ to SQL . Single()—Selects a single record. . SingleOrDefault()—Selects a single record or a default instance. The first method assumes there is at least one element to be returned. (If not, you get an exception.) The second method returns null (for a reference type) when no matching element is found. Here’s a sample query that retrieves the only record where the movie Id has the value 1: MyDatabaseDataContext db = new MyDatabaseDataContext(); Movie result = db.Movies.SingleOrDefault(m => m.Id == 1); if (result != null) Response.Write(result.Title); This query returns a single object of type Movie. If there is no movie record that matches the query, result is null, and the value of the Movie Title property is not written. NOTE When you execute a query that returns a single result, there is no deferred query exe- cution. The LINQ query is translated into a SQL command and executed immediately. Performing a LIKE Select You can perform the equivalent of a LIKE Select with LINQ to SQL in several ways. First, you can use String methods such as Length, Substring, Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, and PadLeft with LINQ to SQL queries. For example, the following query returns all movies that start with the letter t: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies.Where(m=>m.Title.StartsWith(“t”)); Behind the scenes, this query is translated into a SQL query that uses the LIKE operator: SELECT [t0].[Id], [t0].[CategoryId], [t0].[Title], [t0].[Director], [t0].[DateReleased], [t0].[InTheaters], [t0].[BoxOfficeTotals], [t0].[Description] FROM [dbo].[Movie] AS [t0] WHERE [t0].[Title] LIKE @p0 An alternative, more flexible way to make LIKE queries is to use the System.Data.Linq.SqlClient.SqlMethods.Like() method: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.Movies.Where(m=>SqlMethods.Like(m.Title, “t%”)); Using the SqlMethods.Like() method is more flexible than using the standard String methods because you can add as many wildcards to the match pattern as you need. 20 From the Library of Wow! eBook ptg 968 CHAPTER 20 Data Access with LINQ to SQL NOTE The SqlMethods class also contains a number of useful methods for expressing the SQL DateDiff() function in a LINQ to SQL Query. Paging Through Records Doing database paging right when working with ADO.NET is difficult. The SQL language is not designed to make it easy to retrieve a range of records. Doing database paging using LINQ to SQL queries, on the other hand, is trivial. You can take advantage of the following two query methods to perform database paging: . Skip()—Enables you to skip a certain number of records. . Take()—Enables you to take a certain number of records. For example, the class in Listing 20.18 contains a method named SelectedPaged() that gets a particular page of movie records from the Movie database table. LISTING 20.18 Standard\App_Code\Movie.cs using System; using System.Collections.Generic; using System.Linq; using System.Data.Linq; public partial class Movie { public static IEnumerable<Movie> Select() { MyDatabaseDataContext db = new MyDatabaseDataContext(); return db.Movies; } public static IEnumerable< Movie> SelectPaged ( int startRowIndex, int maximumRows ) { return Select().Skip(startRowIndex).Take(maximumRows); } public static int SelectCount() { From the Library of Wow! eBook ptg 969 Performing Standard Database Commands with LINQ to SQL return Select().Count(); } } I’m assuming, in the case of Listing 20.18, that you have already created a Movie entity by using the LINQ to SQL Designer. The Movie class in Listing 20.18 is a partial class that extends the existing Movie class generated by the Designer. The ASP.NET page in Listing 20.19 illustrates how you can use the Movie class with the ObjectDataSource control to page through movie records. LISTING 20.19 Standard\ShowPagedMovies.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html xmlns=”http://www.w3.org/1999/xhtml”> <head runat=”server”> <title>Show Paged Movies</title> </head> <body> <form id=”form1” runat=”server”> <div> <asp:GridView id=”grdMovies” DataSourceID=”srcMovies” AllowPaging=”true” PageSize=”5” Runat=”server” /> <asp:ObjectDataSource id=”srcMovies” TypeName=”Movie” SelectMethod=”SelectPaged” SelectCountMethod=”SelectCount” EnablePaging=”true” Runat=”server” /> </div> </form> </body> </html> 20 From the Library of Wow! eBook ptg 970 CHAPTER 20 Data Access with LINQ to SQL Joining Records from Different Tables You can perform joins when selecting entities just like you can when joining database tables. For example, imagine that you want to join the Movie and MovieCategory tables on the CategoryId key. Assuming that you have both a Movie and MovieCategory entity, you can use the following query: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = db.MovieCategories .Join(db.Movies, c=>c.Id, m=>m.CategoryId, (c,m)=>new {c.Id,c.Name,m.Title}); This LINQ query gets translated into the following SQL command: SELECT [t0].[Id], [t0].[Name], [t1].[Title] FROM [dbo].[MovieCategory] AS [t0] INNER JOIN [dbo].[Movie] AS [t1] ON [t0].[Id] = [t1].[CategoryId] This query performs an inner join. If you want to perform an outer join, the syntax is a little more complicated. Here’s how you do a left outer join using query syntax: MyDatabaseDataContext db = new MyDatabaseDataContext(); var query = from c in db.MovieCategories join m in db.Movies on c.Id equals m.CategoryId into cm from m in cm.DefaultIfEmpty() select new { c.Id, c.Name, m.Title }; This LINQ query gets translated into the following SQL SELECT: SELECT [t0].[Id], [t0].[Name], [t1].[Title] AS [value] FROM [dbo].[MovieCategory] AS [t0] LEFT OUTER JOIN [dbo].[Movie] AS [t1] ON [t0].[Id] = [t1].[CategoryId] As an alternative to using joins, consider taking advantage of the associations between entities. Remember that the following type of query is perfectly valid: MyDatabaseDataContext db = new MyDatabaseDataContext(); var category = db.MovieCategories.Single( c => c.Name == “Drama” ); var query = category.Movies; Caching Records Getting caching to work with LINQ to SQL is a little tricky. Remember that a LINQ to SQL query represents a query expression and not the actual query results. The SQL command is not executed, and the results are not retrieved until you start iterating through the query results. For example, imagine that you declare the following ObjectDataSource control in a page and that this ObjectDataSource control represents a class that returns a LINQ to SQL query: From the Library of Wow! eBook ptg 971 Performing Standard Database Commands with LINQ to SQL <asp:ObjectDataSource id=”srcMovies” TypeName=”Movie” SelectMethod=”Select” EnableCaching=”true” CacheDuration=”9999” Runat=”server” /> This ObjectDataSource has been set up to cache its results. Its EnableCaching and CacheDuration properties are set. However, what gets cached here is the query expression and not that actual query results. The SQL select statement that corresponds to the LINQ to SQL query executes every time the page is requested. To get caching to work, we need to force the query results and not the query into the cache. The Movie class in Listing 20.20 contains a SelectCached() method that successfully caches database data with a LINQ to SQL query. LISTING 20.20 Standard\App_Code\Movie.cs using System; using System.Web; using System.Collections.Generic; using System.Linq; using System.Data.Linq; public partial class Movie { public static IEnumerable<Movie> Select() { MyDatabaseDataContext db = new MyDatabaseDataContext(); return db.Movies; } public static IEnumerable<Movie> SelectCached() { HttpContext context = HttpContext.Current; List<Movie> movies = (List<Movie>)context.Cache[“Movies”]; if (movies == null) { movies = Select().ToList(); context.Cache[“Movies”] = movies; context.Trace.Warn(“Retrieving movies from database”); } return movies; } } 20 From the Library of Wow! eBook ptg 972 CHAPTER 20 Data Access with LINQ to SQL The SelectCached() method attempts to retrieve movie records from the cache. If the records can’t be retrieved from the cache, the movies are retrieved from the database. The vast majority of the time, the movies are retrieved from the cache. The trick here is to use the ToList() method to convert the IEnumerable<Movie> into a List<Movie>. When the List<Movie> is created, the SQL query associated with the LINQ to SQL query is executed and the actual data is returned. You can use the class in Listing 20.20 with the ASP.NET page in Listing 20.21. LISTING 20.21 Standard\ShowCachedMovies.aspx <%@ Page Language=”C#” Trace=”true” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html xmlns=”http://www.w3.org/1999/xhtml”> <head runat=”server”> <title>Show Cached Movies</title> </head> <body> <form id=”form1” runat=”server”> <div> <asp:GridView id=”grdMovies” DataSourceID=”srcMovies” Runat=”server” /> <asp:ObjectDataSource id=”srcMovies” TypeName=”Movie” SelectMethod=”SelectCached” Runat=”server” /> </div> </form> </body> </html> The ObjectDataSource in Listing 20.21 does not have caching enabled. All the caching happens in the Data Access layer (the Movie class). From the Library of Wow! eBook ptg 973 Performing Standard Database Commands with LINQ to SQL Inserting with LINQ to SQL There are two steps to adding and inserting a new record with LINQ to SQL. First, you need to use the InsertOnSubmit() method to add an entity to an existing table. Next, you call SubmitChanges() on the DataContext to execute the SQL INSERT statement against the database. The class in Listing 20.22 illustrates how you can write a method to add a new record into the Movie database table. LISTING 20.22 Standard\App_Code\Movie.cs using System; using System.Web; using System.Collections.Generic; using System.Linq; using System.Data.Linq; public partial class Movie { public static int Insert(Movie movieToInsert) { MyDatabaseDataContext db = new MyDatabaseDataContext(); db.Movies.InsertOnSubmit( movieToInsert ); db.SubmitChanges(); return movieToInsert.Id; } public static IEnumerable<Movie> Select() { MyDatabaseDataContext db = new MyDatabaseDataContext(); return db.Movies.OrderByDescending(m=>m.Id); } } The Movie class includes an Insert() method that inserts a new movie into the database. The Insert() method returns an integer that represents the identity value of the new record. As soon as SubmitChanges() is called, the Id property is updated with the new identity value from the database. NOTE I’m assuming in this section that you have used the LINQ to SQL Designer to create entities for the Movie and MovieCategories database tables. 20 From the Library of Wow! eBook . Listing 20.20 with the ASP. NET page in Listing 20.21. LISTING 20.21 StandardShowCachedMovies.aspx <%@ Page Language=”C#” Trace=”true” %> <!DOCTYPE html PUBLIC -/ /W3C//DTD XHTML 1.0. control to page through movie records. LISTING 20.19 StandardShowPagedMovies.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC -/ /W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>. ); The expression new {m.Id, m.Title} creates an anonymous type that has two properties: Id and Title. The names of the properties of the anonymous type are inferred. If you want to be more explicit,