Beginning C# 2005 Databases PHẦN 8 pot

53 359 0
Beginning C# 2005 Databases PHẦN 8 pot

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

The primary key and Name column are then retrieved from the obtained data reader, and the reader is closed: reader.Read(); Guid rowId = (Guid)reader[“EnchantmentId”]; string name = reader[“Name”] as string; reader.Close(); Next, a command is created to modify the Name column for the retrieved row — the string “ (modified)“ is added. After that, the connection is closed, because no further database access is required: // Modify row. SqlCommand cmd = new SqlCommand( “UPDATE Enchantment SET Name = @Name WHERE EnchantmentId = “ + “@EnchantmentId”, conn); cmd.Parameters.Add(“EnchantmentId”, SqlDbType.UniqueIdentifier).Value = rowId; cmd.Parameters.Add(“Name”, SqlDbType.VarChar, 300).Value = name + “ (modified)“; int rowsModified = cmd.ExecuteNonQuery(); conn.Close(); Finally, a message box is displayed noting the result of the modification: // Notify user. if (rowsModified == 1) { // Success. MessageBox.Show(“Row with Name value of ‘“ + name + “‘ modified.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Information); } else { // Failure. MessageBox.Show(“Row modification failed.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } } Purely for the purpose of brevity, this code doesn’t include a lot of the error checking/exception han- dling that you would normally incorporate for database access. When the application is run and the button is clicked, a modification is made to the database. When a change is attempted on the same row using the data-bound DataGridView control, the operation fails because optimistic concurrency detects that it would result in a loss of data. A DBConcurrencyException exception is thrown, and because it is not handled, the application crashes. 345 Transactions and Concurrency 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 345 Now that you have seen how to implement optimistic concurrency in database access, it’s time to look at how to deal with situations where violations occur, including what to do when you receive a DBConcurrencyException exception. Concurrency Violations in Client Applications When a concurrency violation occurs in your application you have two options: ignore it or act on it. Generally speaking, ignoring it isn’t really viable. That confuses users, and possibly results in inconsis- tencies between database data and the data that users see. Taking things to the next level, you can sim- ply report that an error has occurred and reject changes that users have made — either all changes as a block, or just the changes that caused violations. Again, this isn’t ideal because changes made by users will be lost. A much better choice is to provide users with options when violations occur. For example, you might use the decision tree shown in Figure 9-4 to decide what to do next. In this diagram, diamonds indicate decisions made in your code, namely checking for concurrency vio- lations, checking to see if the external edit resulted in a row being deleted, and checking to see if the requested action is to delete the row. The rounded rectangles indicate the action to take (if any). Figure 9-4: Concurrency decisions User views data User modifies and saves data Concurrency violation? Row exists? Row being deleted? Row being deleted? No Yes Yes Yes Yes NoNo No Display current row state to user, prompt to see whether user still wants to delete the row. Display current row state to user, prompt to see whether user still wants to modify the row. Prompt to see whether user wants to add a new row or discard changes and leave row deleted. 346 Chapter 9 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 346 Multiple concurrency violations may occur in a single “save data” operation by the user, so you might have to repeat this test/prompt/act cycle several times. That isn’t possible when you are dealing with DBConcurrencyExceptions because the exception interrupts subsequent database accesses. Instead, you must use the SqlDataAdapter.RowAdapted event. You look at both of these topics in this section, and see how to implement the system displayed in Figure 9-4 in the next section. Be aware that the decisions illustrated here are not the only ones possible, and the design of your appli- cations may dictate the implementation of alternative strategies. However, you need to know the tech- niques described here to achieve them. Refreshing Disconnected Data Before looking into what to do when a concurrency violation occurs, it’s worth noting a technique for avoiding them in the first place, namely keeping client data as up-to-date as possible by periodic (man- ual or automatic) data updates. The problem in doing this with data-bound controls is that refreshing the data source results in the data being re-bound to the control, so that any current edits/selections are lost unless you write quite a lot of code to cater for it. Manual data refreshing is perhaps the preferable option because you can warn users that current unsaved changes will be lost before continuing. A more advanced technique that is possible with SQL Server 2005 is to make use of the service broker to detect changes in the database, and update data automatically if this occurs. Again, with data binding this can lead to loss of changes, and you have to be careful how you implement it. There are classes to help you — SqlDependancy and SqlNotificationRequest —but their use in Windows applications is beyond the scope of this book. In the next chapter, you look in more depth at data dependencies when using cached data in web applications, which is, thankfully, much simpler. DBConcurrencyException For individual data updates through custom commands, or where you don’t want to continue trying to update database data when a violation occurs, the DBConcurrencyException is your friend. In the ear- lier example application, you saw a situation in which this exception was thrown. Now you examine that exception in more detail and see how you can use it to resolve concurrency violations. As well as the normal exception properties, the DBConcurrencyException exception has a property called Row. It can be used to obtain the row that caused an update violation in the form of a DataRow object. When using typed data sets, the object will be an instance of whatever strongly typed row is appropriate, such as EnchantmentRow object in the earlier example. The row stored in this property is a reference to the row in the data set that has caused a violation, and will have a HasErrors property of true and a RowError property matching the Message property of the exception. The row state is avail- able only for update command errors — if you delete a row, no columns in the Row property are avail- able, and you receive an exception if you try to access their values. You can find out if they are available by checking the value of the Row.RowState property, which is DataRowState.Deleted if the row has been deleted. In itself, having this information isn’t enough for you to resolve the concurrency violation because it doesn’t include any information concerning the current state of the database. However, it contains enough information (notably the primary key of the row) for you to obtain whatever additional data you need. For example, on receiving this information you could immediately query the database for an up-to-date version of the row in question and act accordingly. 347 Transactions and Concurrency 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 347 The following code shows a modification that you can apply to the previous example to catch and process the DBConcurrencyException it generates. The behavior here is to reject the change and notify the user — later in this chapter you use a more advanced mechanism to deal with violations. private void enchantmentBindingNavigatorSaveItem_Click(object sender, EventArgs e) { try { this.Validate(); this.enchantmentBindingSource.EndEdit(); this.enchantmentTableAdapter.Update(this.folktaleDBDataSet.Enchantment); } catch (DBConcurrencyException ex) { // Alert user. MessageBox.Show( “Concurrency violation detected, unable to modify row “ + “as it has been modified or deleted. “ + “Changes discarded.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } // Update bound data. this.enchantmentTableAdapter.Fill(this.folktaleDBDataSet.Enchantment); } This code checks only for concurrency violations. If other database restrictions are violated (a foreign key violation, for instance), the exception will go unhandled. The same applies to other errors, such as a failure to connect to the database. You would need to add additional code to check for these errors, by catching exceptions of type SqlException, and act accordingly. SqlDataAdapter.RowUpdated Event Users often make several modifications to data and apply them in one go. Unfortunately, as soon as a DBConcurrencyException exception is thrown, updates cease, making the previous technique unsuit- able for multiple simultaneous updates using data adapters. In fact, the exception handling situation is even worse because updates made before the violation are committed, while updates to be made subse- quently are not. To have more control over this, you must configure the SqlDataAdapter object in use to continue attempting updates when errors occur, and examine the results of individual data updates as they occur. This involves the following: 1. Set the SqlDataAdapter.ContinueUpdateOnError property to true. 2. Add an event handler for the SqlDataAdapter.RowUpdated event, which fires when an attempt is made to update data. 3. Add logic in the event handler to notify the user and provide her with options for the row in question. 348 Chapter 9 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 348 This is easy enough when using your own data adapters, but it’s slightly more difficult when using wiz- ard-created typed data sets. That’s because the wizard creates its own table adapter class, which wraps the SqlDataAdapter object used to interact with the database. The class created does not expose the RowUpdated event or allow you to set the ContinueUpdateOnError property. However, the table adapter class created is a partial class definition, and you can add your own code to deal with this problem. In the following example you add to the table adapter class definition, inserting the code required to deal with multiple concurrency violations, although you leave the resolution of these until the next section. Try It Out Optimistic Concurrency 1. Copy the Ex0901 - Optimistic Concurrency directory created in the previous example to a new folder called Ex0902 - RowUpdated Event. Open the solution file from the new direc- tory in Visual C# Express, and rename the solution and project to match the directory name. Save the project. 2. Right-click on FolktaleDBDataSet.xsd and click View Code. Remove the default code added to the new FolktaleDBDataSet.cs file, and add the following code: namespace Ex0901___Optimistic_Concurrency.FolktaleDBDataSetTableAdapters { public partial class EnchantmentTableAdapter { public void SetContinueUpdateOnError() { _adapter.ContinueUpdateOnError = true; } public void RegisterRowUpdatedEventHandler( System.Data.SqlClient.SqlRowUpdatedEventHandler handler) { _adapter.RowUpdated += handler; } } } This code includes the namespace from the earlier example. You can, if you want, change the namespace to one that includes the title of the current exercise, but if you do, you’ll have to change it throughout the project. It’s simpler, for the purposes of demonstration, to leave the namespace unchanged because it makes no difference to the function of the application. 3. Open the Form1.cs code file and modify the code as follows: private void enchantmentBindingNavigatorSaveItem_Click(object sender, EventArgs e) { this.Validate(); this.enchantmentBindingSource.EndEdit(); this.enchantmentTableAdapter.Update(this.folktaleDBDataSet.Enchantment); // Refresh data. this.enchantmentTableAdapter.Fill(this.folktaleDBDataSet.Enchantment); 349 Transactions and Concurrency 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 349 } private void Form1_Load(object sender, EventArgs e) { // TODO: This line of code loads data into the ‘folktaleDBDataSet.Enchantment’ // table. You can move, or remove it, as needed. this.enchantmentTableAdapter.Fill(this.folktaleDBDataSet.Enchantment); // Configure for multiple violation detection and continuation. this.enchantmentTableAdapter.SetContinueUpdateOnError(); this.enchantmentTableAdapter.RegisterRowUpdatedEventHandler( new SqlRowUpdatedEventHandler(AdapterRowUpdated)); } private void AdapterRowUpdated(object sender, System.Data.SqlClient.SqlRowUpdatedEventArgs e) { // Check for problem. if (e.Status == UpdateStatus.ErrorsOccurred) { // Get details Guid rowId = (Guid)e.Command.Parameters[“@Original_EnchantmentId”].Value; string updateAction = (e.Row.RowState == DataRowState.Deleted ? “delete” : “update”); if (e.Errors is DBConcurrencyException) { // Alert user. MessageBox.Show( “Concurrency violation detected, unable to “ + updateAction + “ row with ID ‘“ + rowId + “‘ as the row has been modified or deleted. “ + “Changes discarded.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // Alert user. MessageBox.Show( “Error detected, unable to “ + updateAction + “ row with ID ‘“ + rowId + “‘. Error message: “ + e.Errors.Message + “ Changes discarded.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } 4. Run the application, and click the Modify Table Data button. The message box should appear as before. Click OK to accept it. 350 Chapter 9 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 350 5. Modify the Name column for the first two rows (the first row should be the one indicated by the dialog box). 6. Click the Save Data icon on the form. A dialog box should appear, as shown in Figure 9-5. Figure 9-5: Notification dialog box 7. Click OK and note that the display has been refreshed. The first row should include the suffix “(modified)” as set by the button event handler, and the second row should include the modifi- cation you made. 8. Delete the first row and then click Save Data. A different error should be reported, as shown in Figure 9-6. Figure 9-6: Second notification dialog box 9. Click OK, and close the application and Visual C# Express. How It Works This example shows how to use the SqlDataAdapter.RowUpdated event to detect errors when updat- ing rows without aborting subsequent updates. The code detects both concurrency errors and other types of errors that may occur. Because the wizard technique is used to generate a typed data set along with associated utility classes, it is necessary to extend the class definition for the generated table adapter to implement this functionality. The extension added allows the client application to have a greater degree of access to the underlying SqlDataAdapter used to access database data (stored in a private member called _adapter). Specifically, code is added to set the ContinueUpdateOnError property to true: public void SetContinueUpdateOnError() { _adapter.ContinueUpdateOnError = true; } 351 Transactions and Concurrency 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 351 and to allow client applications to register event handlers for the RowUpdated event by supplying a delegate: public void RegisterRowUpdatedEventHandler( System.Data.SqlClient.SqlRowUpdatedEventHandler handler) { _adapter.RowUpdated += handler; } In the client form, a number of modifications are made. First, code is added to enchantmentBindingNavigatorSaveItem_Click() to refresh data after an update because modifica- tions may not have been committed: this.enchantmentTableAdapter.Fill(this.folktaleDBDataSet.Enchantment); Next, code is added to Form1_Load() to use the new methods added to the table adapter: this.enchantmentTableAdapter.SetContinueUpdateOnError(); this.enchantmentTableAdapter.RegisterRowUpdatedEventHandler( new SqlRowUpdatedEventHandler(AdapterRowUpdated)); The AdapterRowUpdated event handler is then ready to be called for each row update. The first thing to do in this method is to see if an error has occurred. That’s possible using the SqlRowUpdatedEventArgs.Status property, which is of type UpdateStatus: private void AdapterRowUpdated(object sender, System.Data.SqlClient.SqlRowUpdatedEventArgs e) { if (e.Status == UpdateStatus.ErrorsOccurred) { If this is UpdateStatus.ErrorsOccurred, the next step is to obtain information about the row that caused the error. While the row properties might not be available directly through SqlRowUpdatedEventArgs.Row (because the row might have been deleted, and this property is set according to the same rules as the one in DBConcurrencyException), it is possible to access the com- mand used to update (or delete) the row. Whether it’s an update or delete command, it has a parameter called @Original_EnchantmentId that you can use to discover the ID of the row being modified. You can also discover the modification being attempted by using the Row.Rowstate property as discussed earlier in the chapter in the context of DBConcurrencyException: Guid rowId = (Guid)e.Command.Parameters[“@Original_EnchantmentId”].Value; string updateAction = (e.Row.RowState == DataRowState.Deleted ? “delete” : “update”); The next thing to look at is the error that occurred, which is available in the SqlRowUpdatedEventArgs .Errors property. For concurrency errors, this will be of type DBConcurrencyException — the excep- tion is included here rather than being thrown, as in earlier examples. The code uses this to report an error (although no attempt is made to deal with the error at this stage): if (e.Errors is DBConcurrencyException) { 352 Chapter 9 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 352 // Alert user. MessageBox.Show( “Concurrency violation detected, unable to “ + updateAction + “ row with ID ‘“ + rowId + “‘ as the row has been modified or deleted. “ + “Changes discarded.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } Alternatively, a different error may have occurred — such as the foreign key violation shown in the example. The remaining code in this method deals with alternative exception types stored in Error: else { // Alert user. MessageBox.Show( “Error detected, unable to “ + updateAction + “ row with ID ‘“ + rowId + “‘. Error message: “ + e.Errors.Message + “ Changes discarded.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } When the application runs, exceptions are not thrown when update errors occur. Instead, the code in the event handler detects errors, including concurrency violations, and reports them. Resolving Concurrency Violations Previous sections have demonstrated how to design your applications to allow for concurrency, and how to detect violations of concurrency when they occur. The next step is to do something about viola- tions when they happen, rather than simply discarding changes. At the beginning of the last section you saw a flow chart detailing the decisions that need to be taken into account and suggesting how you might approach resolutions. In this section you implement that scheme. Actually, this is more about straight programming than database trickery. The code that you will see in this section doesn’t introduce anything earth-shatteringly new, but uses techniques you’ve already investigated extensively and puts them together in a way that achieves the desired result. For this reason it’s best to dive straight in with some sample code. Try It Out Resolving Concurrency Violations 1. Copy the Ex0902 - RowUpdated Event directory created in the previous example to a new folder called Ex0903 - Resolving Violations. Open the solution file from the new direc- tory in Visual C# Express, and rename the solution and project to match the directory name. Save the project. 353 Transactions and Concurrency 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 353 2. Open FolktaleDBDataSet.xsd in DataSet Designer view. 3. Right-click EnchantmentTableAdapter and select Add|Query. Use the Use SQL Statements and SELECT Which Returns Rows options, and add the following query: SELECT EnchantmentId, Name, LastModified FROM dbo.Enchantment WHERE i EnchantmentId = @EnchantmentId 4. Click Next, uncheck Fill a DataTable, and change the name of the Return a DataTable method to GetEnchantmentByID. Click Next and then click Finish. 5. Right-click EnchantmentTableAdapter and select Add|Query. Use the Use SQL Statements and DELETE options, and add the following query: DELETE FROM dbo.Enchantment WHERE EnchantmentId = @EnchantmentId 6. Click Next and change the name of the query to DeleteEnchantmentById. Click Next and then click Finish. 7. Right-click EnchantmentTableAdapter and select Add|Query. Use the Use SQL Statements and UPDATE options, and add the following query: UPDATE dbo.Enchantment SET Name = @Name WHERE EnchantmentId = @EnchantmentId 8. Click Next and change the name of the query to UpdateEnchantmentById. Click Next and then click Finish. 9. Right-click EnchantmentTableAdapter and select Add|Query. Use the Use SQL Statements and INSERT options, and add the following query: INSERT INTO dbo.Enchantment (EnchantmentId, Name) VALUES (@EnchantmentId, @Name) 10. Click Next and change the name of the query to InsertEnchantment. Click Next and then click Finish. 11. Add the following methods to Form1.cs: private void ProcessNonConcurrencyError(Exception error, string updateAction, Guid rowId) { // Alert user. MessageBox.Show( “Error detected, unable to “ + updateAction + “ row with ID ‘“ + rowId + “‘. Error message: “ + error.Message + “ Changes discarded.”, “Row modification report”, MessageBoxButtons.OK, MessageBoxIcon.Error); } private FolktaleDBDataSet.EnchantmentRow GetCurrentRowState(Guid rowId) { // Use new query to get current row state. FolktaleDBDataSet.EnchantmentDataTable currentData = enchantmentTableAdapter.GetEnchantmentByID(rowId); 354 Chapter 9 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 354 [...]... commands such as those that make up a stored procedure For example, consider the following commands: DELETE FROM Enchantment WHERE EnchantmentId = ‘c9af27 48- daac-4ef9-b0b1-320b1 483 06df’ DELETE FROM Enchantment WHERE EnchantmentId = ‘124d8dfb-b48a- 481 5-a9db-369912de6da9’ Here, a transaction is created for each of the two DELETE commands, not one for both of them together, even if they are executed as... a foreign key violation), the second will still be performed Now consider the following two commands: DELTE FROM Enchantment WHERE EnchantmentId = ‘c9af27 48- daac-4ef9-b0b1-320b1 483 06df’ DELETE FROM Enchantment WHERE EnchantmentId = ‘124d8dfb-b48a- 481 5-a9db-369912de6da9’ Here the first command contains a syntax error — the keyword DELTE is not a SQL keyword You might think the result would be the same... create and use a simple web service 3 78 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 379 Working with Disconnected Data Try It Out Basic Web Service 1 Open Visual Web Developer Express 2 Create a new web site with the ASP.NET Web Service project template, in the directory C:\BegVC #Databases\ Chapter10\Ex1001 - Basic Web Service, using the File System location and Visual C# language 3 Open Service.cs from... the MSDTC tab, and if the service status is shown as Stopped (see Figure 9 -8) , click Start Figure 9 -8: Starting MSDTC e Test the application again If it still doesn’t work, click the Security Configuration button on the MSDTC tab, enable Network DTC Access, and select No Authentication Required f The application should now work 8 With everything working properly, you should receive the same error message... enchantmentTableAdapter.Fill(folktaleDBDataSet.Enchantment); } 8 9 10 Run the application and click the SQL Transactions button Click OK in the error dialog box that appears, and then check the records in the main form You should see that only two records from the first transaction are added The two records from the second transaction are not added because there was an error adding one of them Close the application and Visual C# Express How It... Transactions Open the solution file from the new directory in Visual C# Express, and rename the solution and project to match the directory name Save the project 2 Open Form1 and change the text on the second button to Automatic Transactions (make the button bigger if the text doesn’t fit) 367 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 3 68 Chapter 9 3 4 Add a project reference to the System.Transactions.dll... enchantmentTableAdapter.Fill(folktaleDBDataSet.Enchantment); } 6 Run the application The first time you click the button you may see the error message similar to the one shown in Figure 9-7 (if you don’t, continue on to Step 8) Figure 9-7: Error notification 3 68 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 369 Transactions and Concurrency 7 If you received the error, it means that either the MSDTC transaction coordinator service is not started,... the previously performed operations being “rolled back” so that the result is the same as if they had never been performed There’s no explicit reference to databases in the preceding paragraph because the subject of transactions is larger than that — databases are just one type of resource to which transactions apply Other resources include files, application state, and so on Internally, each resource... Adding a web service The asmx file for a web service provides a URL for the web service, but the real work is performed using C# code If you use code behind files (the preferable way of doing things), the asmx page will contain just a single line of code: That code defines the file as a web service and links the file to... transaction is created and managed using SQL code in stored procedures ❑ NET transactions: You create and use transactions in C# code As you will see, both types of transactions have their place in application development, although the techniques involved differ dramatically 3 58 44063c09.qxd:WroxBeg 9/12/06 10:40 PM Page 359 Transactions and Concurrency SQL Transactions When you use SQL Server, you are . commands: DELETE FROM Enchantment WHERE EnchantmentId = ‘c9af27 48- daac-4ef9-b0b1-320b1 483 06df’ DELETE FROM Enchantment WHERE EnchantmentId = ‘124d8dfb-b48a- 481 5-a9db-369912de6da9’ Here, a transaction is created. commands: DELTE FROM Enchantment WHERE EnchantmentId = ‘c9af27 48- daac-4ef9-b0b1-320b1 483 06df’ DELETE FROM Enchantment WHERE EnchantmentId = ‘124d8dfb-b48a- 481 5-a9db-369912de6da9’ Here the first command contains. use transactions in C# code. As you will see, both types of transactions have their place in application development, although the techniques involved differ dramatically. 3 58 Chapter 9 44063c09.qxd:WroxBeg

Ngày đăng: 08/08/2014, 18:21

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan