ptg 1794 CHAPTER 45 SQL Server and the .NET Framework FIGURE 45.3 Adding a LINQ to SQL classes file to your Windows Forms sample application. After your new .dbml file is created in your project, Visual Studio’s Object/Relational (O/R) Designer opens up with its blank surface ready. On this surface, you add C# data classes that mirror the tables and relationships in your database. This is the heart of what is known as object/relational mapping (ORM): each class represents a database table. Each instance of that class represents a row in that table. Each property on each instance repre- sents either a column and its value, or a collection of related (via ORM) objects. Changes to these object instances (including updates, insertions, and deletions) once committed, are reflected as changes to the underlying rows in your tables. LINQ to SQL operates on these ORM objects, which are defined in the code of your .dbml file. Using Visual Studio’s View menu, open Server Explorer. Right-click its root node and select Add Connection. Fill out the form to set up a connection to your AdventureWorks2008 database. When done, navigate to that new node in Server Explorer (it is named [ServerName]\[InstanceName].AdventureWorks2008.[SchemaName]). (This new node should have a tiny image of a plug on its icon.) Expand this node and then expand the Tables node to view the tables in your database. Shift-click the following table nodes in Server Explorer and then drag them all to your O/R Designer surface and release the mouse button (answer Yes to the ensuing warning dialog): . Product (Production) . ProductModel (Production) . ProductInventory (Production) . ProductReview (Production) Your O/R Designer surface should now look something like the one in Figure 45.4. The best way to introduce any new technology is with sample code, so let’s jump right in and write a LINQ to SQL query. ptg 1795 Developing with LINQ to SQL 45 FIGURE 45.4 Viewing Visual Studio’s O/R Designer surface for the Windows Forms sample application. Right-click your Windows Forms project in Solution Explorer and then click Add Class. Name your new class LINQExamples.cs and then add the code from Listing 45.2 into its body. LISTING 45.2 Your First LINQ to SQL Query public List<Product> GetProductsById(int ProductId) { AdventureWorks2008DataContext Context = new AdventureWorks2008DataContext(); var query = from p in Context.Products where p.ProductID == ProductId select p; return query.ToList(); } ptg 1796 CHAPTER 45 SQL Server and the .NET Framework Going Deeper LINQ requires an understanding of generics (introduced in .NET 2.0), which provide a means of working with classes in a type-independent manner. That is, a generic class provides methods and properties just like any other class; however, it also has a type para- meter that enables users of that class to supply a type at runtime that the algorithms in the class will then operate on. In Listing 45.2, for example, the return type of GetProductsById uses the generic Framework class System.Collections.Generic.List<T>, substituting Product as a type parameter for T. When working with LINQ to SQL, you also use the new var keyword, which indicates that the named variable is implicitly typed. This means that the compiler will infer the type of the statement on the right by walking down the expression at compile time. In many cases, your LINQ statements end up being implicitly typed as System.Linq.IQueryable<T>. This generic class stores the abstract expression tree that will be translated (at execution time) to T-SQL, information about the query provider (in this case SQL Server), as well as the enumerable collection itself that provides access to the data the query returns. IQueryable<T> itself derives from System.Collections.Generic.IEnumerable<T>. One reason for this is that, under the hood, the compiler converts the query syntax used in Listing 45.2 to actually use generic query operators defined as extension methods to IEnumerable<T>, including Select, Where, OrderBy, Distinct, and so on. (Extension methods provide a means of adding methods to classes that are otherwise sealed, that is, noninheritable). This means that the query in Listing 45.2 could also have been written as var query = Context.Products.Where(p => p.ProductID == ProductId); Because of their common return type (IEnumerable<T>), LINQ’s extension methods may be chained together. For example: var query = Context.Products.Where(p => p.ProductID == ProductId).OrderBy(p => p.ProductID).Distinct(); The parameter to the Where and OrderBy methods shown here is built on another new construct called a lambda expression, which is an anonymous function that can be cast to a delegate (such as System.Func<T, TResult>, the delegate type required by the input para- meter to the extension method Where). Lambda expressions take the form input parame- ters => expression , where the lambda operator => is read as “goes to.” (For more information, refer to the MSDN article titled “Lambda Expressions.”) Put simply, these expressions provide a compact syntax for writing anonymous functions that you will use more often as you progress deeper in your knowledge of LINQ. For now, let’s get back to the code in Listing 45.2. First, in the method signature, you can see that GetProductsById takes an integral ProductId as input and returns a generic List of Product objects. What is a Product ptg 1797 Developing with LINQ to SQL 45 object? It’s a LINQ to SQL class instance that points (is object/relationally mapped) to a particular row in the Production.Product table. You can think of a Product object as an “objectified” SQL Server row, insofar as its primi- tively typed properties are akin to SQL Server column values. It also has collection-backed properties that point to rows stored in the tables to which it is related. These specialized LINQ collections are of type System.Data.Linq.EntitySet. Let’s examine the class defini- tion of Product to see how this plays out. Using Solution Explorer, expand the AdventureWorks2008.dbml file to reveal AdventureWorks2008.designer.cs. This code file contains all the ORM logic needed to use LINQ to SQL with your database. Double-click this file and, using the drop-down at the top left of your code editor window, select [YourProjectName].Product. Above your class’s name, notice the Table attribute (attributes are .NET classes used to decorate other classes with information queryable at runtime). Its Name property reveals that the class it decorates is to be mapped to rows in Production.Product. As you scroll down, examine this class a bit more. Notice how its primitive properties map nicely to the columns of your table. Notice also how each property has its own Column attribute, signi- fying the specific SQL Server column to which it is mapped. As we mentioned earlier, the Product class also has collection-backed properties, such as ProductInventories and ProductReviews. These properties represent the tables related (via primary and foreign keys) to Product. By using properties in your .NET code, you can navigate from a parent object in Product to a child object in ProductInventories, just like you would when writing T-SQL joins. Not surprisingly, each Association attribute found on your collection-backed properties denotes the direction of the navigational path from parent to child. The glue that holds all this together is the System.Linq.DataContext class, represented in Listing 45.2 by your Context object (which is of type AdventureWorks2008DataContext). Put simply, DataContext is the ORM bridge or conduit from .NET to SQL Server and back. At the top of your designer file, you can see that the DBML class inherits from System.Data.Linq.DataContext. Notice its mapping attribute, Database, which indicates that it is mapped to AdventureWorks2008. As the SQL Server conduit, you use the DataContext instance to select your object-mapped rows. Through it, you commit inserts, updates, and deletes to the underlying tables, by adding, changing properties of, and removing objects from its System.Data.Linq.Table collection’s properties. These collec- tions represent the tables you added to your O/R Designer’s surface. And this is the real power of LINQ to SQL: no longer is it necessary to waste hours writing boilerplate T-SQL to perform simple database operations; you can get it all done with pure .NET code. Let’s look at a slightly more complex LINQ query that leverages the power of key-based rela- tionships to select related database objects. In Listing 45.3, you add a new method to the sample class that gets a List of ProductReview objects for a given Product. ptg 1798 CHAPTER 45 SQL Server and the .NET Framework LISTING 45.3 Using Database Relationships to Select Related Objects with LINQ to SQL public List<ProductReview> GetProductReviewsByProduct(Product MyProduct) { AdventureWorks2008DataContext Context = new AdventureWorks2008DataContext(); var query = from p in Context.Products join r in Context.ProductReviews on p.ProductID equals r.ProductID where p.ProductID == MyProduct.ProductId select r; return query.ToList(); } Notice the join syntax introduced in this example. As you can see, it’s a lot like T-SQL’s INNER JOIN, and it performs the same basic function. The where clause in the example is also just like T-SQL’s WHERE, except for the fact that you have to use C#’s == operator instead of T-SQL’s =). The one big difference to pay attention to is that with LINQ your select statement comes last, and your from clause comes first. Remember that all the tables you want to select from are properties ( System.Data.Linq.Table objects) of the DataContext object. Very simple, very powerful. Uncovering LINQ to SQL with Linqpad You may be curious about the T-SQL that LINQ is generating for this .NET expression. To reveal this mystery, you can use two tools: SQL Server Profiler and Linqpad. To use Profiler, launch the application from the Windows Start menu, create a new trace using the File menu (select New Trace), and connect to your database of choice. On the ensuing Trace Properties dialog, select the trace template called T-SQL_SPs, start the trace, and then run the LINQ statement found in Listing 45.3. The T-SQL that LINQ generates is revealed in the TextData column. Alternatively, you can download and use the amazing Linqpad from http://www.linqpad. net. Visit the site and download Linqpad.exe. Save it to a folder that you will remember. As of this writing (version 1.35), Linqpad does not have or need an installation program; the download itself is the entire self-contained application. After you download Linqpad, start it. Click on the Add Connection link at top left of the application’s main window; then add a connection to your local server. Next, select the AdventureWorks2008 database using the drop-down at the top left. In the query window, ptg 1799 Developing with LINQ to SQL 45 type the following LINQ statement (Hint: This is the same as shown in Listing 45.3, minus the method signature, input parameter, and Context object): from p in Products join r in ProductReviews on p.ProductID equals r.ProductID select r Click the green arrow button or press F5. Your query results show up in a visually friendly tabular format in the results area below the query window. Notice just above the results area there is a button bar with four buttons: Results, Δ; (lambda expression), SQL, and IL. You’ve seen what shows up with the default setting, Results. Δ; reveals the underlying anonymous functions upon which LINQ relies. IL shows you the CLR Intermediate Language code that your LINQ expression generates. Finally, SQL shows the resultant T- SQL, of main concern to you now. Select SQL and press F5 again. Your results should match the code shown in Figure 45.5. You can also use the Analyze SQL menu option (above the results area) to jump into SSMS to run your query, or you can even run the T- SQL itself via Linqpad. Next, let’s examine an insert query using LINQ to SQL, as shown in Listing 45.4. FIGURE 45.5 Viewing the T-SQL Generated by a LINQ Query with Linqpad. ptg 1800 CHAPTER 45 SQL Server and the .NET Framework LISTING 45.4 An Insert Query Using LINQ to SQL public void AddProductReview( int ForProductId, string Comments, string Email, int Rating, string Reviewer ) { AdventureWorks2008DataContext Context = new AdventureWorks2008DataContext(); ProductReview NewReview = new ProductReview() { Comments = Comments, EmailAddress = Email, ModifiedDate = DateTime.Now, ProductID = ForProductId, Rating = Rating, ReviewDate = DateTime.Now, ReviewerName = Reviewer }; Context.ProductReviews.InsertOnSubmit(NewReview); Context.SubmitChanges(); //Check the new review ID: if (NewReview.ProductReviewID > 0) { Debug.WriteLine( string.Format( “Success! Added new ProductReview with ID#{0}”, NewReview.ProductReviewID ) ); } } Let’s go over this code in detail. First, you can see that the input parameters to the new AddProductReview method include a ProductReviewId as well as most of the properties that make up a row in Production.ProductReview. Next, using C#’s new new syntax, you instantiate a ProductReview object (NewReview) representing a row to be added to ptg 1801 Developing with LINQ to SQL 45 Production.ProductReview. To make the insertion happen, you again rely on the DataContext object (Context). The syntax Context.ProductReviews indicates that the target table is Production.ProductReview. When you call InsertOnSubmit with your NewReview object as its parameter, your new review is added to the table when you call SubmitChanges (one line further down in the example). After that call, you can check your object to see if its ProductReviewID prop- erty was magically populated due to the fact that the row was created in the database (because ProductReviewID is bound to the table’s primary key, which is an auto-incre- mental identity column). LINQ is great in this way because it keeps the contents of your objects and data tables in sync. The next listing, Listing 45.5, illustrates how to perform a delete using LINQ to SQL. LISTING 45.5 Deleting Rows Using LINQ to SQL public void DeleteProductReview(int ProductReviewId) { AdventureWorks2008DataContext Context = new AdventureWorks2008DataContext(); ProductReview Review = (from m in Context.ProductReviews where m.ProductReviewID == ProductReviewId select m).FirstOrDefault(); if (Review != null) { using (TransactionScope Tran = new TransactionScope()) { Context.ProductReviews.DeleteOnSubmit(Review); Context.SubmitChanges(); } } } To run Listing 45.5, you need to add a reference to System.Transactions and then add a using statement for that namespace. This addition to the code illustrates one way to use transactions with LINQ and also preserves the integrity of your AdventureWorks2008 data. Going over the example, the DeleteProductReview method takes a ProductReviewID value as input. It then looks up that record using a LINQ query, just as you would in T-SQL. If the record was found (that is, if the query returns a non-null value), you then create a new transaction, in which you delete the record by calling DeleteOnSubmit. Note that because you do not call the Complete method of the Tran object, the transaction is implicitly rolled back. ptg 1802 CHAPTER 45 SQL Server and the .NET Framework Listing 45.6 rounds out our LINQ examples by showing you how to update a set of rows. LISTING 45.6 Updating Rows Using LINQ to SQL public void UpdateProductInventories(int Threshold, short NewQty) { AdventureWorks2008DataContext Context = new AdventureWorks2008DataContext(); List<ProductInventory> InventoryItems = (from m in Context.ProductInventories where m.Quantity < Threshold select m).ToList(); if (InventoryItems.Count > 0) { using (TransactionScope Tran = new TransactionScope()) { foreach (ProductInventory Item in InventoryItems) { Item.Quantity = NewQty; } Context.SubmitChanges(); } } } In this listing, the query operates on a range of rows, rather than just one. Using LINQ’s ORM magic, you select rows from Production.ProductInventory as a List of ProductInventory by matching your Threshold criterion against the current Quantity value of objects in DataContext.ProductInventories. (Notice how LINQ even performs grammatically correct pluralization of table names.) You iterate through each returned object (again, within the scope of a transaction so as to keep AdventureWorks2008 intact) and then update the Quantity for each. Then you submit your changes. Very simple, very powerful, very much a timesaver. Although this LINQ to SQL primer covers the essentials, we highly recommend you dive deeper into the .NET namespaces you’ve seen to uncover all the possibilities LINQ to SQL has to offer. ptg 1803 Using ADO.NET Data Services 45 Using ADO.NET Data Services ADO.NET Data Services (ADODS) is a platform for providing SQL Server data to websites, RIAs (such as Silverlight and Flash applications), and other Internet clients over standard HTTP using modern web development conventions. Getting Set Up To start using ADODS, you first need to download and install Visual Studio 2008 Service Pack 1, as well as the .NET Framework 3.5 Service Pack 1. These updates include support for the Microsoft Entity Framework (EF), ADODS core, and ADODS .NET client library. A second configuration step, required for viewing ADODS XML data in the examples that follow, is an Internet Explorer 8 settings change: using the Tools menu, click Internet Options. Click the Content tab and then click the Settings button in the Feeds and Web Slices group. On the Feeds and Web Slices dialog, uncheck the Turn on Feed Reading View check box. Click OK twice. Essentials To work with data provided by ADODS services, you make HTTP requests, each of which must include three key parts: . A uniform resource identifier (URI), which addresses the data in question . An HTTP verb (either GET, POST, MERGE, PUT, or DELETE), which indicates the type of CRUD operation to be performed . An HTTP Accept header, which indicates the format of the data being sent or received As of this writing, ADODS services provide data to clients in one of two formats: . Atom Publishing Protocol (AtomPub)—An XML format that acts as an applica- tion-level protocol for working with web resources. (This is the default ADODS response format. For more information on Atom, visit http://en.wikipedia.org/wiki/Atom_(standard).) . JavaScript Object Notation (JSON)—A text-based format for representing serial- ized JavaScript objects. Many popular Asynchronous JavaScript and XML (AJAX) client libraries (including JQuery, Prototype, and YUI) include core support for work- ing with JSON. ADODS services rely on EF to provide an abstract mapping layer between a physical data model and CLR object model. EF is a general-purpose ORM (similar to a LINQ to SQL class) that works with a number of data providers, including SQL Server, Oracle, DB2, and MySQL. The first step in working with ADODS is to create an EF Entity Data Model (EDM) that includes the objects you want to expose from the AdventureWorks2008 database to the Web. . connection to your AdventureWorks2008 database. When done, navigate to that new node in Server Explorer (it is named [ServerName][InstanceName].AdventureWorks2008.[SchemaName]). (This new node should. table. You can think of a Product object as an “objectified” SQL Server row, insofar as its primi- tively typed properties are akin to SQL Server column values. It also has collection-backed properties. powerful. Uncovering LINQ to SQL with Linqpad You may be curious about the T -SQL that LINQ is generating for this .NET expression. To reveal this mystery, you can use two tools: SQL Server Profiler and