Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 57 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
57
Dung lượng
1,65 MB
Nội dung
■ ■ ■ PART 5 LINQ to SQL Rattz_789-3.book Page 379 Tuesday, October 16, 2007 2:21 PM Rattz_789-3.book Page 380 Tuesday, October 16, 2007 2:21 PM 381 ■ ■ ■ CHAPTER 12 LINQ to SQL Introduction Listing 12-1. A Simple Example Updating the ContactName of a Customer in the Northwind Database // Create a DataContext. Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); // Retrieve customer LAZYK. Customer cust = (from c in db.Customers where c.CustomerID == "LAZYK" select c).Single<Customer>(); // Update the contact name. cust.ContactName = "Ned Plimpton"; try { // Save the changes. db.SubmitChanges(); } // Detect concurrency conflicts. catch (ChangeConflictException) { // Resolve conflicts. db.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges); } ■Note This example requires generation of entity classes, which I will cover later in this chapter. In Listing 12-1, I used LINQ to SQL to query the record whose CustomerID field is "LAZYK" from the Northwind database Customers table and to return a Customer object representing that record. I then updated the Customer object’s ContactName property and saved the change to the Northwind database by calling the SubmitChanges method. That’s not much code considering it is also detecting concurrency conflicts and resolving them if they occur. Run Listing 12-1 by pressing Ctrl+F5. There is no console output, but if you check the database, you should see that the ContactName for customer LAZYK is now "Ned Plimpton". Rattz_789-3.book Page 381 Tuesday, October 16, 2007 2:21 PM 382 CHAPTER 12 ■ LINQ TO SQL INTRODUCTION ■Note This example makes a change to the data in the database without changing it back. The original value of the ContactName for customer LAZYK is "John Steel". You should change this back so that no subsequent examples behave improperly. You could change it manually, or you could just change the example code to set it back, and run the example again. This book uses an extended version of the Northwind database. Please read the section in this chapter titled “Obtaining the Appropriate Version of the Northwind Database” for details. Introducing LINQ to SQL At this point, I have discussed using LINQ with in-memory data collections and arrays, XML, and DataSets. Now, I will move on to what many seem to feel is the most compelling reason to use LINQ, LINQ to SQL. I say that because when I look at the MSDN forum for LINQ, the majority of the posts seem to focus on LINQ to SQL. I think many developers are overlooking the significance of LINQ as a general purpose query language and the multitude of ways it can be utilized. Hopefully, I have convinced you of this already through the previous chapters. LINQ to SQL is an application programming interface (API) for working with SQL Server data- bases. In the current world of object-oriented programming languages, there is a mismatch between the programming language and the relational database. When writing an application, we model classes to represent real-world objects such as customers, accounts, policies, and flights. We need a way to persist these objects so that when the application is restarted, these objects and their data are not lost. However, most production-caliber databases are still relational and store their data as records in tables, not as objects. A customer class may contain multiple addresses and phone numbers stored in collections that are child properties of that customer class; once persisted, this data will most likely be stored in multiple tables, such as a customer table, an address table, and a phone table. Additionally, the data types supported by the application language differ from the database data types. Developers left to their own devices are required to write code that knows how to load a customer object from all of the appropriate tables, as well as save the customer object back to the appropriate tables, handling the data type conversion between the application language and the database. This is a tedious, and often error-prone, process. Because of this object-relational mapping (ORM) problem, often referred to as the object-relational impedance mismatch, a plethora of prewritten ORM software solutions have been designed through the years. LINQ to SQL is Microsoft’s entry-level LINQ-enabled ORM implementation for SQL Server. Notice that I said “for SQL Server.” LINQ to SQL is exclusive to SQL Server. LINQ, however, is not, and hopefully, other database vendors are or will be at work implementing their own LINQ APIs. I personally would like to see a LINQ to DB2 API, and I am sure many others would like to see LINQ to Oracle, LINQ to MySQL, LINQ to Sybase, and perhaps others. ■Note LINQ to SQL only works with SQL Server or SQL Express. To use LINQ with other databases, additional LINQ APIs will need to be written by the appropriate database vendors. Until then, or perhaps as an alternative, consider using LINQ to DataSet. You may have also noticed that I said LINQ to SQL is an entry-level ORM implementation. If you find it is not powerful or flexible enough to meet your requirements, you may want to investigate LINQ to Entities. While I do not cover LINQ to Entities in this book, it is alleged to be more powerful and flexible than LINQ to SQL. Be aware, though, that the increase in power comes coupled with additional complexity. Also, LINQ to Entities is not as mature as LINQ to SQL. Rattz_789-3.book Page 382 Tuesday, October 16, 2007 2:21 PM CHAPTER 12 ■ LINQ TO SQL INTRODUCTION 383 Most ORM tools attempt to abstract the physical database into business objects. With that abstraction, we typically lose the ability to perform SQL queries, which is a large part of the attraction to relational databases. This is what separates LINQ to SQL from many of its contemporaries. Not only do we get the convenience of business objects that are mapped to the database, we get a full- blown query language, similar to the already familiar SQL, thrown in to boot. ■Tip LINQ to SQL is an entry-level ORM tool that permits powerful SQL queries. In addition to providing LINQ query capabilities, as long as your query returns LINQ to SQL entity objects, as opposed to returning single fields, named nonentity classes, or anonymous classes, LINQ to SQL also provides change tracking and database updates, complete with optimistic concur- rency conflict detection and resolution, and transactional integrity. In Listing 12-1, I first had to instantiate an instance of the Northwind class. That class is derived from the DataContext class, and I will cover this class in-depth in Chapter 16. For now, consider it a supercharged database connection. It also handles updating the database for us, as you can see when I later call the SubmitChanges method on it. Next, I retrieved a single customer from the Northwind database into a Customer object. That Customer object is an instantiation of the Customer class, which is an entity class that either had to be written or generated. In this case, the Customer class was generated for me by the SQLMetal utility, as was the Northwind class for that matter. After retrieving the customer, I updated one of the Customer object’s properties, ContactName, and called the SubmitChanges method to persist the modified contact name to the database. Please notice that I wrapped the call to the SubmitChanges method in a try/catch block and specifically caught the ChangeConflictException exception. This is for handling concurrency conflicts, which I will cover in detail in Chapter 17. Before you can run this example or any of the others in this chapter, you will need to create entity classes for the Northwind database. Please read the section in this chapter titled “Prerequisites for Running the Examples” to guide you through creation of the necessary entity classes. LINQ to SQL is a complex subject, and to provide any example requires involving many LINQ to SQL elements. In the first example at the beginning of this chapter, I am utilizing a derived DataContext class, which is the Northwind class; an entity class, which is the Customer class; concurrency conflict detection and resolution; and database updates via the SubmitChanges method. I can’t possibly explain all these concepts simultaneously. So, I need to give you some background on each of these compo- nents before I begin so that you will have a basic understanding of the foundation of LINQ to SQL. Rest assured that I will cover each of these concepts in agonizing detail later in the subsequent LINQ to SQL chapters. The DataContext The DataContext is the class that establishes a connection to a database. It also provides several services that provide identity tracking, change tracking, and change processing. I’ll cover each of these services in more detail in Chapter 16. For now, just know that it is the DataContext class that is connecting us to the database, monitoring what we have changed, and updating the database when we call its SubmitChanges method. It is typical with LINQ to SQL to use a class derived from the DataContext class. The name of the derived class typically is the same as the database it is mapped to. I will often refer to that derived class in the LINQ to SQL chapters as [Your]DataContext, because its name is dependent on the data- base for which it is being created. In my examples, my derived DataContext class will be named Northwind, because it was gener- ated by the SQLMetal command-line tool, and SQLMetal names the generated, derived DataContext class after the database for which it is generated. Rattz_789-3.book Page 383 Tuesday, October 16, 2007 2:21 PM 384 CHAPTER 12 ■ LINQ TO SQL INTRODUCTION This derived DataContext class, [Your]DataContext, will typically have a Table<T> public prop- erty for each database table you have mapped in the database, where T will be the type of the entity class that is instantiated for each retrieved record from that particular database table. The data type Table<T> is a specialized collection. For example, since there is a Customers table in the Northwind database, my Northwind class derived from the DataContext class will have a Table<Customer> named Customers. This means that I can access the records in the Customers database table by directly accessing the Customers property of type Table<Customer> in my Northwind class. You can see an example of this in the first example in this chapter, Listing 12-1, where I coded db.Customers. That code is querying the records in the Customers table of the Northwind database. Entity Classes LINQ to SQL involves using entity classes, where each entity class is typically mapped to a single database table. However, using entity class inheritance mapping, it is possible to map an entire class hierarchy to a single table under special circumstances. You can read more about this in Chapter 18. So, we have entity classes mapping to database tables, and the entity class properties get mapped to table columns. This entity class-to-table and property-to-column mapping is the essence of LINQ to SQL. ■Note The essence of LINQ to SQL is mapping entity classes to database tables and entity class properties to database table columns. This mapping can occur directly in class source files by decorating classes with the appropriate attributes, or it can be specified with an external XML mapping file. By using an external XML mapping file, the LINQ-to-SQL-specific bits can be kept external to the source code. This could be very handy if you don’t have source code or want to keep the code separated from LINQ to SQL. For the majority of examples in the LINQ to SQL chapters, I will be using entity classes that have been generated by the SQLMetal command-line tool. SQLMetal generates the entity classes with the LINQ to SQL mapping bits right in the source module it generates. These mapping bits are in the form of attributes and attribute properties. You will be able to detect the existence of entity classes in my examples when you see classes or objects that have the singular form of a Northwind database table name. For example, in Listing 12-1, I use a class named Customer. Because Customer is the singular form of Customers and the Northwind database has a table named Customers, this is your clue that the Customer class is an entity class for the Northwind database’s Customers table. The SQLMetal command- line tool has an option called /pluralize that causes the entity classes to be named in the singular form of the database table name. Had I not specified the /pluralize option when generating my entity classes, my entity class would be named Customers, as opposed to Customer, because the name of the table is Customers. I mention this in case you get confused reading other writings about LINQ to SQL. Depending on how the author ran the SQLMetal tool and what options were specified, the entity class names may be plural or singular. Associations An association is the term used to designate a primary key to foreign key relationship between two entity classes. In a one-to-many relationship, the result of an association is that the parent class, the class containing the primary key, contains a collection of the child classes, the classes having the Rattz_789-3.book Page 384 Tuesday, October 16, 2007 2:21 PM CHAPTER 12 ■ LINQ TO SQL INTRODUCTION 385 foreign key. That collection is stored in a private member variable of type EntitySet<T>, where T will be the type of the child entity class. For example, in the Customer entity class generated by the SQLMetal command-line tool for the Northwind database, there is a private member of type EntitySet<Order> named _Orders that contains all of the Order objects for a specific Customer object: private EntitySet<Order> _Orders; SQLMetal also generated a public property named Orders to be used for accessing the private _Orders collection. On the other end of the relationship, the child, which is the class containing the foreign key, contains a reference to the parent class, since that is a many-to-one relationship. That reference is stored in a private member variable of type EntityRef<T>, where T is the type of the parent class. In the generated Northwind entity classes, the Order entity class contains a private member variable of type EntityRef<Customer> named _Customer: private EntityRef<Customer> _Customer; Again, the SQLMetal tool also generated a public property named Customer to provide access to the parent reference. The association, primary and foreign keys, as well as the direction of the relationship are all defined by attributes and attribute properties in the generated entity classes’ source module. The benefit gained by the association is the ability to access a parent’s child classes, and there- fore database records, as easily as accessing a property of the parent class. Likewise, accessing a child’s parent class is as easy as accessing a property of the child class. Concurrency Conflict Detection One of the valuable services that the DataContext is performing for you is change processing. When you try to update the database by calling the DataContext object’s SubmitChanges method, it is auto- matically performing optimistic concurrency conflict detection. If a conflict is detected, a ChangeConflictException exception is thrown. Any time you call the SubmitChanges method, you should wrap that call in a try/catch block and catch the ChangeConflictException exception. This is the proper way to detect concurrency conflicts. You can see an example of this in Listing 12-1. I will go into painstaking detail about concur- rency conflict detection and resolution in Chapter 17. Many of the examples in this and the following LINQ to SQL chapters will not provide concurrency conflict detection or resolution for the sake of brevity and clarity. In your real code, you should always do both. Concurrency Conflict Resolution Once a concurrency conflict has been detected, the next step will be to resolve the concurrency conflict. This can be done in multiple ways. In Listing 12-1, I do it the simplest way by calling the ResolveAll method of the ChangeConflicts collection of the derived DataContext class when the ChangeConflictException exception is caught. Again, in many of the examples in the LINQ to SQL chapters, I will not have code to either detect the concurrency conflicts or to resolve them, but you should always have code handling this in your real production code. As I mentioned in the previous section, I will cover concurrency conflict resolution in detail in Chapter 17. Rattz_789-3.book Page 385 Tuesday, October 16, 2007 2:21 PM 386 CHAPTER 12 ■ LINQ TO SQL INTRODUCTION Prerequisites for Running the Examples Since virtually all of the examples in this and the following LINQ to SQL chapters use Microsoft’s sample extended Northwind database, we will need entity classes and mapping files for the North- wind database. Obtaining the Appropriate Version of the Northwind Database Unfortunately, the standard Microsoft Northwind database is missing a few things I will need to fully show off LINQ to SQL, such as table-valued and scalar-valued functions. Therefore, instead of using the standard Northwind database, I will use an extended version of it that Microsoft initially distrib- uted to demonstrate LINQ. You may obtain the extended version of the Northwind database in the Book Extras section of this book’s page at the Apress web site: http://www.apress.com/book/bookDisplay.html?bID=10241 Or, you may obtain it at my site, LINQDev.com. Look for the section titled Obtain the Northwind Database: http://www.linqdev.com If you download it from LINQDev.com, make sure you download the extended version. To run all the examples in the LINQ to SQL chapters of this book, you will need to download this extended version of the Northwind database. Generating the Northwind Entity Classes Because I have not yet covered how to generate entity classes in detail, I am going to tell you how to generate them without providing much explanation. However, I will cover the details thoroughly in Chapter 13. To generate the entity classes, you must have the extended version of the Northwind database that I discussed in the previous section. Open a Visual Studio command prompt. To do so, look in your Microsoft Visual Studio 2008 menu for a submenu named Visual Studio Tools for an item named Visual Studio 2008 Command Prompt, and select it. Once the command prompt opens, change your current directory to whatever directory in which you desire to create your entity classes and external mapping file. I am going to change my directory to the root of the C: drive: cd \ If you are going to generate your entity classes using the Northwind database files without first attaching the database to them, use the following command: sqlmetal /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views <path to Northwind MDF file> ■Caution Pay particular attention to the MDF file name and its casing, as you specify it on the command line. The name and case of the DataContext derived class that is generated will match the file name that is passed on the command line, not the physical file name itself. If you deviate from a DataContext derived class name of Northwind, none of the examples will work without modification. Therefore, it is critical that you pass the North- wind database file name as [path]\Northwind.mdf, not northwind.mdf, NorthWind.mdf, or any other variation of the name. Rattz_789-3.book Page 386 Tuesday, October 16, 2007 2:21 PM CHAPTER 12 ■ LINQ TO SQL INTRODUCTION 387 To create entity classes from a file named Northwind.mdf, enter the following command: sqlmetal /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views "C:\Northwind.mdf" Running this command will cause an entity class module named Northwind.cs to be created for you in the current directory. If you are going to generate your entity classes from the Northwind database that is already attached to your SQL Server, use the following command: sqlmetal /server:<server> /user:<user> /password:<password> /database:Northwind /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views To create entity classes from an attached database named Northwind, enter the following command: sqlmetal /server:.\SQLExpress /database:Northwind /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views ■Note Depending on your environment, you may need to specify a user with the /user:[username] option and a password with the /password:[password] option on the command line in the preceding example. Please read the section titled “SQLMetal” in Chapter 13 for more details. The command entered using either of these approaches tells SQLMetal to generate the source code into a file named Northwind.cs in the current directory. I will cover all the program’s options in the next chapter. Copy the generated Northwind.cs file into your project by adding it as an existing item. You may now utilize LINQ to SQL on the Northwind database using the entity classes contained in the Northwind.cs file. ■Tip Be cautious of making changes to the generated entity class source file. You may find you need to regenerate it at some later point, causing you to lose any changes. You may desire to add business logic by adding methods to the entity classes. Instead of modifying the generated file, consider taking advantage of C# 2.0 partial classes to keep the added properties and methods in a separate source module. Generating the Northwind XML Mapping File I will also need to generate a mapping file that some of the examples will use. Again, I will use SQLMetal for this purpose. So, from the same command line and path, execute the following command: sqlmetal /map:northwindmap.xml "C:\Northwind.mdf" /pluralize /functions /sprocs /views /namespace:nwind Again, pay close attention to the casing used to specify the MDF file. This will generate a file named northwindmap.xml into the current directory. ■Note This command will echo code to the screen as well as generating the XML mapping file, so don’t be alarmed when you see the code on the screen. Rattz_789-3.book Page 387 Tuesday, October 16, 2007 2:21 PM 388 CHAPTER 12 ■ LINQ TO SQL INTRODUCTION Using the LINQ to SQL API In order to use the LINQ to SQL API, you will need to add the System.Data.Linq.dll assembly to your project if it is not already there. Also, if they do not already exist, you will need to add using directives to your source module for the System.Linq and System.Data.Linq namespaces like this: using System.Data.Linq; using System.Linq; Additionally, for the examples, you will need to add a using directive for the namespace the entity classes were generated into, nwind: using nwind; IQueryable<T> You will see that in many of the LINQ to SQL examples in this chapter and the subsequent LINQ to SQL chapters, I work with sequences of type IQueryable<T>, where T is the type of an entity class. These are the type of sequences that are typically returned by LINQ to SQL queries. They will often appear to work just like an IEnumerable<T> sequence, and that is no coincidence. The IQueryable<T> interface implements the IEnumerable<T> interface. Here is the definition of IQueryable<T>: interface IQueryable<T> : IEnumerable<T>, IQueryable Because of this inheritance, you can treat an IQueryable<T> sequence like an IEnumerable<T> sequence. Some Common Methods As you will soon see, many of the examples in the LINQ to SQL chapters become complex very quickly. Demonstrating a concurrency conflict requires making changes to the database external to LINQ to SQL. Sometimes, I need to retrieve data externally of LINQ to SQL too. To highlight the LINQ to SQL code and to eliminate as many of the trivial details as possible, while at the same time providing real working examples, I have created some common methods to use in many of the examples. Be sure to add these common methods to your source modules as appropriate when testing the examples in the LINQ to SQL chapters. GetStringFromDb() A common method that will come in handy is a method to obtain a simple string from the database using standard ADO.NET. This will allow me to examine what is actually in the database, as opposed to what LINQ to SQL is showing me. GetStringFromDb: a Method for Retrieving a String Using ADO.NET static private string GetStringFromDb( System.Data.SqlClient.SqlConnection sqlConnection, string sqlQuery) { if (sqlConnection.State != System.Data.ConnectionState.Open) { sqlConnection.Open(); } Rattz_789-3.book Page 388 Tuesday, October 16, 2007 2:21 PM [...]... thing as triple-clicking either, but that’s what it appears to be responding to Or, you can select the entity class property on the canvas and edit the Name property in the Properties window Editing an Entity Class Property’s (Entity Class Member’s) Properties (Settings) You can edit an entity class property’s properties by selecting the property on the canvas and editing the appropriate property in. .. canvas and editing the Name property in the Properties window Editing the Entity Class’s Properties (Entity Class Settings) You can edit the properties, as in settings, of the entity class by selecting the entity class on the canvas and editing the appropriate properties in the Properties window, of which the entity class name is one You have the ability to edit the database table name in which these... by right-clicking the entity class in the designer and selecting the Property menu item in the Add context menu Once the property has been added to the entity class, follow the directions for editing an entity class property’s properties in the section above named “Editing an Entity Class Property’s (Entity Class Member’s) Properties (Settings).” Adding a New Association Instead of using drag and drop... Before proceeding, make sure you are viewing the Northwind.dbml file in the Visual Studio editor Adding an Entity Class Find your Northwind database in the list of Data Connections in the Server Explorer window Expand the Tables node, and you should be presented with a list of tables in the Northwind database Entity classes are created by dragging tables from the Table list in the Server Explorer window... when instantiating the DataContext This allows LINQ to SQL to be used without any actual LINQ to SQL source code being compiled with your code Interestingly, if you specify both the /code and /map options in the same invocation of SQLMetal, you will get code generated without LINQ to SQL attributes Of course, you would use the also generated map with the generated code to be able to use LINQ to SQL /language: ... ■ LINQ TO SQL TIPS AND TOOLS Amazingly, all my method arguments were already mapped by default to the appropriate class properties Nice! Once you have mapped all of the method arguments, click the OK button You are now ready to insert Customer records using the InsertCustomer stored procedure In Listing 13-3, I will create a new customer using the InsertCustomer stored procedure Listing 13-3 Creating... Point] @p6: Input String (Size = 2; Prec = 0; Scale = 0) [FL] @p7: Input String (Size = 5; Prec = 0; Scale = 0) [32346] @p8: Input String (Size = 3; Prec = 0; Scale = 0) [USA] @p9: Input String (Size = 14; Prec = 0; Scale = 0) [ (80 0) EAT-WICH] @p10: Input String (Size = 14; Prec = 0; Scale = 0) [ (80 0) FAX-WICH] @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) [] Context: SqlProvider(Sql2005)... Input String (Size = 15; Prec = 0; Scale = 0) [Alligator Point] @p6: Input String (Size = 2; Prec = 0; Scale = 0) [FL] @p7: Input String (Size = 5; Prec = 0; Scale = 0) [32346] @p8: Input String (Size = 3; Prec = 0; Scale = 0) [USA] @p9: Input String (Size = 14; Prec = 0; Scale = 0) [ (80 0) EAT-WICH] @p10: Input String (Size = 14; Prec = 0; Scale = 0) [ (80 0) FAX-WICH] Context: SqlProvider(Sql2005)... NorthwindDataContext() : base(global::LINQChapter13.Properties.Settings.Default.NorthwindConnectionString, mappingSource) { OnCreated(); } ■Caution If you download the companion source code for this book, make sure you update the connectionString setting in the app.config file Specifically, the data source will contain my machine name, which will most likely not match your machine name Notice in the... using partial classes to keep your LINQ to SQL database attributes separate from your business logic, you will minimize the need to add code back to any generated entity class code Alternatively, you could have your business classes and your LINQ to SQL entity mapping decoupled by using an external XML mapping file This is an XML file that maps business objects to the database without relying on LINQ . SQL, thrown in to boot. ■Tip LINQ to SQL is an entry-level ORM tool that permits powerful SQL queries. In addition to providing LINQ query capabilities, as long as your query returns LINQ to SQL. be at work implementing their own LINQ APIs. I personally would like to see a LINQ to DB2 API, and I am sure many others would like to see LINQ to Oracle, LINQ to MySQL, LINQ to Sybase, and. application programming interface (API) for working with SQL Server data- bases. In the current world of object-oriented programming languages, there is a mismatch between the programming language