ptg 934 CHAPTER 19 Building Data Access Components with ADO.NET Runat=”server” /> <hr /> <asp:GridView id=”grdMovies” DataSourceID=”srcMovies” Runat=”server” /> <asp:ObjectDataSource id=”srcMovies” TypeName=”RandomDataLayer” SelectMethod=”GetRandomMovies” Runat=”server” /> </div> </form> </body> </html> Summary This chapter provided you with an overview of ADO.NET. It described how you can use ADO.NET to represent database data with both a connected and disconnected model of data access. In the first part, you learned how to use the Connection, Command, and DataReader objects to connect to a database, execute commands, and represent the results of a database query. You learned how to retrieve provider statistics such as command execution times. You also learned how to represent stored procedures with the Command object. Finally, you learned how to work with multiple active resultsets (MARS). In the second part, you learned how to work with the DataAdapter, DataTable, DataView, and DataSet objects. You learned how you can perform batch updates with the DataAdapter object. You also learned how to use the DataTable object to represent and edit database rows. Next, you learned how to improve the data access performance of your ASP.NET pages by executing asynchronous database commands within asynchronous ASP.NET pages. Finally, you got a chance to tackle the advanced topic of building database objects with .NET Framework. You learned how you can use .NET Framework to build both user- defined types and stored procedures. For example, you learned how to insert and select a custom class from a database table by creating a user-defined type with .NET Framework. From the Library of Wow! eBook ptg CHAPTER 20 Data Access with LINQ to SQL IN THIS CHAPTER . New C# and VB.NET Language Features . Creating LINQ to SQL Entities . Performing Standard Database Commands with LINQ to SQL . Creating a Custom LINQ Entity Base Class . Summary A vast chasm separates the way developers work with transient application data and the way developers work with persistent database data. In our applications, we work with objects and properties (created with either C# or VB.NET). In most databases, on the other hand, we work with tables and columns. This is true even though our applications and our databases represent the very same data. For example, you might have both a class and a database table named Product that repre- sents a list of products you sell through your website. However, the languages we use to interact with these enti- ties are different. The C# and VB.NET languages are differ- ent from the SQL language. Larger companies typically have different developers who specialize in C# or VB.NET, on the one hand, or SQL, on the other hand. A huge amount of developer time is spent performing brain-dead, tedious translations between the object and relational universes. I cringe when I think of the number of hours I’ve spent declaring classes that contain a one-to-one mapping between properties and database columns. This is time I could have devoted to going to the park with my children, seeing a movie, walking my dog, and so on. LINQ to SQL promises to finally enable us to put SQL to a well-deserved death. Or more accurately, it promises to make SQL a subterranean language that we never need to interact with again. (SQL is plumbing, and I am not a plumber.) This is a good thing. Death to SQL! This is a hard chapter. LINQ to SQL is not easy to under- stand because it relies on several mind-bending features From the Library of Wow! eBook ptg 936 CHAPTER 20 Data Access with LINQ to SQL introduced into C#, VB.NET, and .NET Framework. So please, have patience. Take a deep breath. Everything will make sense in the end. This chapter is divided into four parts. In the first part, I discuss the features introduced in C#, VB.NET, and .NET Framework that support LINQ. Next, you learn how to represent database tables with LINQ to SQL entities. In the following part, I explain how to perform standard SQL commands—such as SELECT, INSERT, UPDATE, and DELETE—with LINQ to SQL. In the final part of this chapter, I demonstrate how you can create a custom entity base class (and integrate form validation into your LINQ entities). New C# and VB.NET Language Features To get LINQ to SQL to work, Microsoft had to introduce several new language features to both C# and VB.NET. Many of these features make C# and VB.NET behave more like a dynamic language (think JavaScript). Although the primary motivation for introducing these new features was to support LINQ, the new features are also interesting in their own right. NOTE LINQ was introduced in ASP.NET 3.5. These language features will not work on websites targeting .NET Framework 1.1, 2.0, or 3.0. Understanding Automatic Properties The first of these language features we explore is automatic properties, which unfortunately is supported only by C# and not VB.NET. Automatic properties provide you with a short- hand method for defining a new property. For example, Listing 20.1 contains a class named Product that contains Id, Description, and Price properties. LISTING 20.1 LanguageChanges\App_Code\AutomaticProperties.cs public class AutomaticProperties { // Automatic Properties public int Id { get; set; } public string Description { get; set; } // Normal Property private decimal _Price; From the Library of Wow! eBook ptg 937 New C# and VB.NET Language Features public decimal Price { get { return _Price; } set { _Price = value; } } } The first two properties, Id and Description, unlike the last property, Price, do not include getters or setters. The C# compiler creates the getters and setters—and the secret, private, backing fields—for you automatically. You can’t add any logic to the getters and setters for an automatic property. You also can’t create read-only automatic properties. Why are automatic properties relevant to LINQ to SQL? When working with LINQ to SQL, you often use classes to represent nothing more than the list of columns you want to retrieve from the database (the shape of the data) like the select list in a SQL query. In those cases, you just want to do the minimum amount of work possible to create a list of properties, and automatic properties enable you to do this. NOTE You c an quickly add an automatic proper ty to a class or page when using Visua l Web Developer/Visual Studio by typing prop and pressing the Tab key twice. Understanding Initializers You can use initializers to reduce the amount of work it takes to create a new instance of a class. For example, assume that you have a class that looks like Listing 20.2 (in C#) or like Listing 20.3 (in VB.NET). LISTING 20.2 LanguageChanges\App_Code\Product.cs public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } 20 From the Library of Wow! eBook ptg 938 CHAPTER 20 Data Access with LINQ to SQL LISTING 20.3 LanguageChanges\App_Code\Product.vb Public Class Product Private _Id As Integer Public Property Id() As Integer Get Return _Id End Get Set(ByVal value As Integer) _Id = value End Set End Property Private _Name As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Private _Price As Decimal Public Property Price() As Decimal Get Return _Price End Get Set(ByVal value As Decimal) _Price = value End Set End Property End Class The Product class has three public properties (declared by taking advantage of automatic properties in the case of C#—sorry VB.NET). Now, let’s say you want to create an instance of the Product class. Here’s how you would do it in .NET Framework 2.0 (with C#): From the Library of Wow! eBook ptg 939 New C# and VB.NET Language Features Product product1 = new Product(); product1.Id = 1; product1.Name = “Laptop Computer”; product1.Price = 800.00m; And here is how you would do it in .NET Framework 2 (with VB.NET): Dim product1 As New Product() product1.Id = 1 product1.Name = “Laptop Computer” product1.Price = 800.0 It takes four lines of code to initialize this trivial little Product class. That’s too much work. By taking advantage of initializers, you can do everything in a single line of code. Here’s how you use initializers in C#: Product product2 = new Product {Id=1, Name=”Laptop Computer”, Price=800.00m}; Here’s how you use initializers in VB.NET: Dim product2 As New Product() With {.Id = 1, .Name = “Laptop Computer”, ➥ .Price = 800.0} Now, clearly, you could do something similar by declaring a constructor on the Product class that accepts Id, Name, and Price parameters. However, then your class would become more bloated with code because you would need to assign the constructor parameter values to the class properties. Initializers are useful because, by taking advantage of this feature, you can declare agile, little classes and initialize these svelte classes with a minimum of code. Understanding Type Inference Here’s a feature that makes C# and VB.NET look much more like a dynamic language such as JavaScript: local variable type inference. When you take advantage of type inference, you allow the C# or VB.NET compiler to determine the type of a variable at compile time. Here’s an example of how you use type inference with C#: var message = “Hello World!”; And here is how you would use type inference with VB.NET: Dim message = “Hello World!” The message variable is declared without specifying a type. The C# and VB.NET compilers can infer the type of the variable (it’s a String) from the value you use to initialize the variable. No performance impact results from using type inference. (The variable is not late bound.) The compiler does all the work of figuring out the data type at compile time. 20 From the Library of Wow! eBook ptg 940 CHAPTER 20 Data Access with LINQ to SQL A new keyword was introduced in ASP.NET 3.5 to support type inference: the var keyword. You declare a variable as type var when you want the compiler to figure out the variable’s data type all by itself. You can take advantage of type inference only when you provide a local variable with an initial value. For example, this won’t work (C#): var message; message = “Hello World!”; The C# compiler will refuse to compile this code because the message variable is not initialized when it is declared. The following code will work in VB.NET (but it won’t do what you want): Dim message message = “Hello World!” In this case, VB.NET will treat the message variable as type Object. At runtime, it will cast the value of the variable to a string when you assign the string to the variable. This is not good from a performance perspective. NOTE VB.NET includes an Option Infer option that must be enabled for the implicit typing fea- ture to work. You can enable it for a particular class file by adding the line Option Infer On at the top of a code file. The relevance of type inference to LINQ to SQL will be apparent after you read the next section. In many circumstances when using LINQ to SQL, you won’t actually know the name of the type of a variable, so you have to let the compiler infer the type. Understanding Anonymous Types Anonymous types is another idea that might be familiar to you from dynamic languages. Anonymous types are useful when you need a transient, fleeting type and you don’t want to do the work to create a class. Here’s an example of creating an anonymous type in C#: var customer = new {FirstName = “Stephen”, LastName = “Walther”}; Here’s how you would create the same anonymous type in VB.NET: Dim customer = New With {.FirstName = “Stephen”, .LastName = “Walther”} From the Library of Wow! eBook ptg 941 New C# and VB.NET Language Features The customer variable is used without specifying a type, which looks very much like JavaScript or VBScript. However, you need to understand that customer does have a type; you just don’t know its name: It’s anonymous. In a single line of code, we’ve managed to both create a new class and initialize its proper- ties. The terseness brings tears to my eyes. Anonymous types are useful when working with LINQ to SQL because you’ll discover that you often need to create new types on-the-fly. For example, you might want to return a class that represents a limited set of database columns when performing a particular query. You need to create a transient class that represents the columns. Understanding Generics Generics are not a new feature; however, they are such an important aspect of LINQ to SQL that it is worth using a little space to review this feature. NOTE To us e generics, you need to impor t the System.Collections.Generic namespace. I most often use generics by taking advantage of generic collections. For example, if you want to represent a list of strings, you can declare a list of strings like this (in C#): List<string> stuffToBuy = new List<string>(); stuffToBuy.Add(“socks”); stuffToBuy.Add(“beer”); stuffToBuy.Add(“cigars”); Here’s how you would declare the list of strings in VB.NET: Dim stuffToBuy As New List(Of String) stuffToBuy.Add(“socks”) stuffToBuy.Add(“beer”) stuffToBuy.Add(“cigars”) And, by taking advantage of collection initializers, you can now declare a strongly typed list of strings in a single line like this (in C#): List<string> stuffToBuy2 = new List<string> {“socks”, “beer”, “cigars”}; NOTE Unfortunately, VB.NET does not support collection intializers or array initializers. 20 From the Library of Wow! eBook ptg 942 CHAPTER 20 Data Access with LINQ to SQL The List class is an example of a generic because you specify the type of object that the class will contain when you declare the List class. In C#, you specify the type in between the alligator mouths (< >), and in VB.NET you use the Of keyword. In the preceding examples, we created a List class that contains strings. Alternatively, we could have created a List class that contains integers or a custom type such as products or customers represented by a Product or Customer class. A generic collection such as a List is superior to a nongeneric collection such as an ArrayList because a generic is strongly typed. An ArrayList stores everything as an object. A generic stores everything as a particular type. When you pull an item out of an ArrayList, you must cast it to a particular type before you use it. An item pulled from a generic, on the other hand, does not need to be cast to a type. Generics are not limited solely to collections. You can create generic methods, generic classes, and generic interfaces. For example, when working with ADO.NET classes, I like to convert my data readers into strongly typed List collections. The method GetListFromCommand(), shown in Listing 20.4, takes a command object, executes it, and generates a typed List automatically. LISTING 20.4 LanguageChanges\App_Code\GenericMethods.cs using System; using System.Collections.Generic; using System.Data.SqlClient; public class GenericMethods { public static List<T> GetListFromCommand<T>(SqlCommand command) where T: ICreatable, new() { List<T> results = new List<T>(); using (command.Connection) { command.Connection.Open(); SqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { T newThing = new T(); newThing.Create(reader); results.Add( newThing ); } } return results; } } From the Library of Wow! eBook ptg 943 New C# and VB.NET Language Features public interface ICreatable { void Create(SqlDataReader reader); } The GetListFromCommand() method in Listing 20.4 accepts a SqlCommand object and returns a generic List<T>. The generic type is constrained by the where clause. The generic constraint restricts the types that can be used for T to types that implement the ICreatable interface and types that can be instantiated with new. The ICreatable interface is also defined in Listing 20.4. The interface requires a class to implement a single method named Create(). Now that we have created a generic method for converting data readers into strongly typed lists, we can use it with any class that implements the ICreatable interface, such as the Movie class in Listing 20.5. LISTING 20.5 Movie.cs using System; using System.Data.SqlClient; public class Movie : ICreatable { public int Id { get; set; } public string Title { get; set; } public void Create(SqlDataReader reader) { Id = (int)reader[“Id”]; Title = (string)reader[“Title”]; } } You can call the generic GetListFromCommand() method with the Movie type like this (the page named ShowGenericMethods.aspx on the book’s website uses this code): string conString = WebConfigurationManager.ConnectionStrings[“con”]. ➥ ConnectionString; SqlConnection con = new SqlConnection(conString); SqlCommand cmd = new SqlCommand(“SELECT Id, Title FROM Movie”, con); List<Movie> movies = GenericMethods.GetListFromCommand<Movie>(cmd); 20 From the Library of Wow! eBook . Price properties. LISTING 20.1 LanguageChangesApp_CodeAutomaticProperties.cs public class AutomaticProperties { // Automatic Properties public int Id { get; set; } public string Description. Features Product product1 = new Product(); product1.Id = 1; product1.Name = “Laptop Computer”; product1.Price = 800.00m; And here is how you would do it in .NET Framework 2 (with VB .NET) : Dim product1. C#: Product product2 = new Product {Id=1, Name=”Laptop Computer”, Price=800.00m}; Here’s how you use initializers in VB .NET: Dim product2 As New Product() With {.Id = 1, .Name = “Laptop Computer”,