186 Microsoft ADO.NET 4 Step by Step Table and Column Mapping Sometimes it isn’t convenient or even possible to use the same table and column names between your local DataTable and the external database table, view, or generated results it represents. In such situations, use the SqlDataAdapter.TableMappings collection to define the naming changes between the external and internal versions of your table structures. This system is especially useful when importing data into a DataSet in which named tables already exist. Remember that when SqlDataAdapter retrieves result sets from the database, it names the first set “Table,” the second set “Table1,” and so on. For example, suppose you use the DataSet / String syntax in the Fill method: workAdapter.Fill(targetSet, "Grid") ' Visual Basic version The first table in targetSet is “Grid,” the second is “Grid1,” and so on. If the DataSet includes names that vary from these defaults, Fill will add new tables with the default names and schemas. To coerce the data adapter into moving the incoming records into the correct table, add new System.Data.Common.DataTableMapping objects to the table mapping collection before call- ing the Fill method. C# // Using the basic external-internal syntax is quick. workAdapter.TableMappings.Add("Table", "Employee"); // Adding a DataTableMapping instance works also. DataTableMapping nameChange = new DataTableMapping(); nameChange.SourceTable = "Table1"; nameChange.DataSetTable = "Customer"; workAdapter.TableMappings.Add(nameChange); Visual Basic ' Using the basic external-internal syntax is quick. workAdapter.TableMappings.Add("Table", "Employee") ' Adding a DataTableMapping instance works also. Dim nameChange As New DataTableMapping nameChange.SourceTable = "Table1" nameChange.DataSetTable = "Customer" workAdapter.TableMappings.Add(nameChange) Chapter 11 Making External Data Available Locally 187 It’s not just table names that can be mapped; the data adapter supports column name map- pings as well. C# // Start with the table name. DataTableMapping employeeMap = workAdapter.TableMappings.Add("Table", "Employee"); // Then add the columns. Columns not mentioned here // import using the source column names. employeeMap.ColumnMappings.Add("Employee ID", "ID"); employeeMap.ColumnMappings.Add("Current Department", "DeptID"); Visual Basic ' Start with the table name. Dim employeeMap As DataTableMapping = workAdapter.TableMappings.Add("Table", "Employee") ' Then add the columns. Columns not mentioned here ' import using the source column names. employeeMap.ColumnMappings.Add("Employee ID", "ID") employeeMap.ColumnMappings.Add("Current Department", "DeptID") You are not required to set up a mapping for every incoming table or column. The data adapter includes two properties that establish the rules for handling missing targets. The SqlDataAdapter.MissingMappingAction property determines what should be done when the table and column mapping rules do not include one of the incoming tables or columns (or any of them). It is set to one of the MissingMappingAction enumerated values. MissingMappingAction.Passthrough Even though there is no mapping, the table or column is added to the target schema using the default incoming name. This is the default setting. MissingMappingAction.Ignore The missing table or column is ignored, and its in- coming data for that table or column is discarded. MissingMappingAction.Error The mapping process generates an exception when it finds an unmapped table or column. 188 Microsoft ADO.NET 4 Step by Step The second property for managing mapping exceptions is the SqlDataAdapter.Missing SchemaAction. This property indicates what should be done when the target DataSet or DataTable does not already include the incoming mapped or unmapped table or column name. Its options somewhat parallel those used for the missing mapping action. MissingSchemaAction.Add Any table or column names missing from the target schema are added automatically. This is the default setting. MissingSchemaAction.AddWithKey The same as the MissingSchemaAction.Add option, but primary key and other constraint settings are imported along with the basic schema. MissingSchemaAction.Ignore Any incoming table or column names not already found in the target schema are discarded, and the data values associated with those elements are excluded from import. MissingSchemaAction.Error The import process generates an exception on any missing table or column in the schema. The target schema is not modified at all. Summary This chapter was a culmination of all that you gained through the earlier chapters. In the SqlDataAdapter (and its siblings for other providers), the data sets, tables, connections, com- mands, and parameters come together to provide a flexible and consistent method for im- porting data easily from an external data source and migrating modifications to that data back out to the database. The SqlDataAdapter class enables this data communication through a set of four platform- specific statements: SELECT, INSERT, UPDATE, and DELETE, or their stored procedure equiva- lents. ADO.NET includes the SqlCommandBuilder tool to assist you in developing at least some of these statements. For more advanced implementations, the data adapter’s table mappings feature lets you control how data comes into memory-resident storage. Chapter 11 Making External Data Available Locally 189 Chapter 11 Quick Reference To Do This Import a database table into a DataTable through an adapter Create the DataTable instance. Create a SqlDataAdapter, supplying both a record selec- tion query and a valid connection. Call the adapter’s Fill method, passing it the DataTable instance. Return modified records to a database through an adapter Create a SqlDataAdapter, supplying both a record selec- tion query and valid connection. Provide the adapter’s InsertCommand, UpdateCommand, and DeleteCommand, either directly or by using a SqlCommandBuilder. Call the adapter’s Fill method, passing it the DataTable or DataSet instance as needed. Make changes to the data. Call the adapter’s Update method, passing it the DataTable or DataSet instance. Prevent incoming data from modifying the existing DataSet schema Set the SqlDataAdapter instance’s MissingSchemaAction property to MissingSchemaAction.Ignore. 191 Chapter 12 Guaranteeing Data Integrity After completing this chapter, you will be able to: Understand ADO.NET’s use of optimistic concurrency Perform transactions that include multiple record updates Spread transactions across multiple databases Database programming would be a meaningless task if there were no way to guarantee the integrity of the data. Having a single user update a single record with no one else to interrupt the process is one thing. But what happens when you have dozens—or hundreds—of users all trying to update records in the same tables, or even the same records, at the same time? Welcome to the world of transactions—database operations that enable multiple record updates to be treated as a single unit. This chapter introduces ADO.NET’s take on the trans- action and how your code can work with the database to ensure safe and sound data. Note The exercises in this chapter all use the same sample project: a program that simulates the transfer of funds between bank accounts. Although you will be able to run the application after each exercise, the expected results for the full application might not appear until you complete all exercises in the chapter. Transactions and Concurrency In today’s Web-based, highly-scalable 24/7/365 world, it’s a given that multiple users will attempt to simultaneously modify the content in your database. As long as each user is up- dating different records, concerns about data conflicts occurring between those users are minimal. But when two users start competing for the same records, the safety of the data itself becomes a serious issue. Consider two users, Alice and Bob, who are using the same event reservations system to pur- chase tickets for an upcoming concert. Because the seats for the concert are numbered, only a single user can purchase a numbered ticket for a specific seat. The sales system sells tickets in two steps: (1) it reads the reservations table to locate the next empty seat, and (2) it up- dates the record to assign a user to the previously looked-up seat. 192 Microsoft ADO.NET 4 Step by Step Figure 12-1 shows three possible scenarios when Alice and Bob use the system at approxi- mately the same time. Alice: Read Scenario #1 Alice: Read Scenario #2 Alice: Read Alice: Write Bob: Read Bob: Read Bob: Read Alice: Write Bob: Write Bob: Write Bob: Write Alice: Write Scenario #3 FIGURE 12-1 The risks of multiuser access to data. Assume that seats 100 and 101 are available and will be reserved in that order. In Scenario #1, Alice completes her transaction for seat 100 before Bob even begins requesting an open seat, so there aren’t any data conflicts. But Scenarios #2 and #3 show potential problems. Depending on how the system is designed, it’s possible that either Bob or Alice will be with- out a reservation. In Scenario #3, if the system tells both Alice and Bob that 100 is the next seat available, Alice will get the reservation even through Bob updated the system first. This “last one wins” situation is a common difficulty to be overcome in database development. Another potential problem occurs when a database update takes place in multiple parts. When you transfer money from your savings account to your checking account, the database records (at least) two distinct updates: (1) the debit of funds from the savings account, and (2) the credit of those same funds into the checking account. As long as both operations oc- cur, the record of the transfer is sound. But what happens if the database crashes after the withdrawal of funds from the savings account, but before those funds make it into the check- ing account? Databases attempt to resolve all these conflicts and more by employing transactions. A transaction is a single unit of database work that is guaranteed to maintain the integrity and reliability of the data. It does this by adhering to ACID, the four rules that define the transac- tion, which are as follows: Atomicity The transaction is all or nothing. If any part of the transaction cannot com- plete, whether due to invalid data, constraint limitations, or even a hardware failure, the entire transaction is cancelled and undone. After this reversal completes, the state of the involved records is the same as if the transaction never occurred. Chapter 12 Guaranteeing Data Integrity 193 Consistency The transaction, when complete, will leave the database in a valid state. This aspect of transactions often details with constraints. For example, when deleting a parent record, child records bound by a foreign key constraint must be modified to remove the parent reference, deleted from the database, or if they remain, the transac- tion must be canceled. Isolati on This property has to do with multiuser scenarios. When a transaction is active, other users or processes that attempt to access the involved records are not al- lowed to see those records in a semicomplete state. The database must either provide those other processes with the pretransaction content of the records, or force those processes to block, or wait, until the transaction is complete. Durability Durable transactions are robust enough to overcome any type of database failure, and if they are damaged so that they cannot be recovered, they are ultimately reversed. Modern databases achieve this using transaction logs, a secondary repository of all database modifications that can be “played back” to recover damaged data if needed. Robust databases such as SQL Server ensure that updates made to individual records meet all these ACID requirements. For updates made to multiple records, especially those that involve different tables, ACID applies only if you specifically wrap the updates within the database’s platform-specific implementation of a transaction. A transaction begins when you specifically tell the database that you need one, and it ends when you either commit the transaction—making all changes that took place within the transaction permanent—or issue a rollback—a cancelling or reversal of the entire transaction. Database systems also employ record locking: the temporary protection of records, record blocks, or entire tables from use by other processes during the lifetime of a transaction or other data operation. The isolation property of ACID is a typical record-locking function, al- though there are other manual and automated actions in which a database will lock records. Record locking allows programmers to resolve the seat reservation issues previously posed by Alice and Bob. If Alice locks seat 100 pending completion of the reservation process, Bob will not have access to that record. Instead, he must either wait until the reservation system makes a seat number available or reserve a seat that is not currently locked. Note Although record locking is a traditional method of protecting records, it is often not ef- ficient, and sometimes not even possible, when dealing with disconnected, highly-scalable sys- tems such as busy web sites. Such systems must use other methods of protecting records that must be restricted to a single user or session. Some of these alternatives are described in this chapter, on page 194. 194 Microsoft ADO.NET 4 Step by Step Concurrency is the art of when to apply a record lock. There are two main flavors of concurrency: Pessimistic concurrency Records destined to be updated are locked when they are first accessed and read. Only the user holding the lock has full access to the record, including the ability to modify it. At some point, the record must be released, either through a formal release of the lock or through an update-and-commit process that completes the update. Pessimistic concurrency is useful when allowing two users up- date access to the same record could prove problematic. Optimistic concurrency Records are left unlocked during the read-write interval and are locked by the database only at the moment of update. This type of record locking is good for those times when records are rarely or never accessed by two users at once, or when the risks associated with having two users update those records in parallel are small. Limited locks allow for high scalability, although with the increased potential for data conflicts. ADO.NET, with its focus on disconnected data processing, uses optimistic concurrency. Unfortunately, this method leaves some applications open to data conflicts of the type expe- rienced by Alice and Bob. There are data-specific methods that help avoid, or even eliminate, these problems, even when pessimistic concurrency is not available. The SqlCommandBuilder class uses one such method when it builds data modification statements for the target data- base table. Consider an update statement that modifies several fields in a customer table: UPDATE Customer SET FullName = @NewName, Address = @NewAddress, Phone = @NewPhone WHERE ID = @OriginalID If User A and User B are both updating the record at the same time, with User A modifying Address and User B correcting Phone, the “last one wins” rule will apply in the absence of pessimistic concurrency. SqlCommandBuilder attempts to reduce such issues by including all the original data values in the update query’s WHERE clause. UPDATE Customer SET FullName = @NewName, Address = @NewAddress, Phone = @NewPhone WHERE ID = @OriginalID AND FullName = @OriginalName AND Address = @OriginalAddress AND Phone = @OriginalPhone This changes the update system to “first one wins” because any changes made to the record will fail to match some of the “original” values submitted by the second user—one that still has the original premodified image of the record—and thus prevent the update request from Chapter 12 Guaranteeing Data Integrity 195 making additional changes without first obtaining the “new original” version of the record. In SQL Server table updates, a rowversion column can be used in the same way because interim updates to the record change that column’s value automatically. /* versiontrack column is of type rowversion. */ UPDATE Customer SET FullName = @NewName, Address = @NewAddress, Phone = @NewPhone WHERE ID = @OriginalID AND versiontrack = @OriginalRowVersion Note Some database platforms support statements that let you sidestep ADO.NET’s preference for optimistic concurrency. The Oracle SELECT statement, for example, includes a FOR UPDATE clause that applies a persistent lock to the record until it is modified in a subsequent statement or is otherwise released. Depending on how you manage your ADO.NET database connections and connection-pooling options, such SQL statements might provide access to true pessimistic concurrency. If you choose to use such features, be sure to fully test your implementation, and be aware of changes to ADO.NET in future releases that might affect your use of such statements. Using Local Transactions ADO.NET includes support for transactions with a single database through the System. Data.Common.DbTransaction class. In the SQL Server provider, this base class is overridden by the System.Data.SqlClient.SqlTransaction class. The OLE DB and ODBC providers imple- ment transactions through the System.Data.OleDb.OleDbTransaction and System.Data. Odbc.OdbcTransaction classes, respectively. Note The remaining discussion of transactions focuses on the SQL Server provider’s imple- mentation. The OLE DB and ODBC implementations are identical, although some of the internal aspects vary by target database. Some OLE DB or ODBC-accessible databases might not support transactions. Using a transaction to enclose multiple update statements is simple: 1. Open a connection to the database with a SqlConnection object. 2. Create a SqlTransaction instance on that connection. 3. Issue SQL statements within the context of the transaction. 4. Either commit or roll back the transaction. 5. Close the database connection. . locate the next empty seat, and (2) it up- dates the record to assign a user to the previously looked-up seat. 192 Microsoft ADO. NET 4 Step by Step Figure 1 2-1 shows three possible scenarios. table or column. 188 Microsoft ADO. NET 4 Step by Step The second property for managing mapping exceptions is the SqlDataAdapter.Missing SchemaAction. This property indicates what should be done. names. employeeMap.ColumnMappings.Add("Employee ID", "ID") employeeMap.ColumnMappings.Add("Current Department", "DeptID") You are not required to set up a mapping