Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 53 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
53
Dung lượng
479,67 KB
Nội dung
The code in the event handler must add a row to the CharacterAddress table to associate the character with an address. As with other rows, a new GUID ID is required, so the first step is to generate one: private void addressView_UserAddedRow(object sender, DataGridViewRowEventArgs e) { Guid characterAddressId = Guid.NewGuid(); Next, the new row is added directly to the typed data set that stores your data — namely the data com- ponent. The FolktaleDBDataSet.CharacterAddress.AddCharacter() method can do this, using an overload that accepts a GUID ID value, a CharacterRow object, an AddressRow object, and a Boolean value determining whether the address is the primary one for the character. You can get both of the row objects (a CharacterRow and an AddressRow) from the Current properties of the relevant binding sources, using the DataRowView.Row property and casting as per usual: data.CharacterAddress.AddCharacterAddressRow(characterAddressId, (characterBindingSource.Current as DataRowView).Row as FolktaleDBDataSet.CharacterRow, (addressBindingSource.Current as DataRowView).Row as FolktaleDBDataSet.AddressRow, false); } The “primary address” aspect of the database isn’t actually used in this example, but it still needs to be set. Once a new row is added, it’s necessary to re-filter the address view to make it display because the GUID ID value of the new address won’t be included in the current filter. This cannot be done as part of the preceding event handler — that would result in problems adding data to the new row. Re-filtering data will lose the current cell selection and make users swear. Instead, the re-filtering is performed when the selected row in the address view changes: private void addressBindingSource_CurrentChanged(object sender, EventArgs e) { SetAddressFilter(); } Now that adding rows is possible, it’s time to look at deleting rows. Here the problem is that multiple characters may reference a single Address row. If you don’t deal with it, deleting an address from one character will result in it being deleted for all characters. In addition, there will still be a row in the CharacterAddress table that references the deleted row, which results in an error when you save changes to the database (a foreign key violation occurs). So, what you have to do is this: ❑ Delete the row from the CharacterAddress table that links the character to the address. ❑ Check to see if there are any remaining rows in the CharacterAddress table that reference the address. If there aren’t, delete the address to prevent it from being orphaned. In this application the UserDeletingRow event handler for the DataGridView control handles these steps. You start by canceling the pending deletion because you are providing your own implementation: private void addressView_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e) { e.Cancel = true; 398 Chapter 10 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 398 Next, you need to find the row in the CharacterAddress table that should be deleted. To do so, you need to find the GUID ID values for the character and address rows. The character row can be found, as in previous code, through the characterBindingSource.Current property: FolktaleDBDataSet.CharacterRow characterRow = (characterBindingSource.Current as DataRowView).Row as FolktaleDBDataSet.CharacterRow; The address row is (indirectly) passed through the event arguments for the event handler. To extract it, you use the Row property of the event arguments, and get the DataRowView that the row is bound to using the Row.DataBoundItem property. This can then be used in the usual way to obtain an AddressRow object. FolktaleDBDataSet.AddressRow addressRow = (e.Row.DataBoundItem as DataRowView).Row as FolktaleDBDataSet.AddressRow; Now that you have the two rows, you can find the CharacterAddress row by using the Select() method of the CharacterAddress table. As with the filtering code you’ve already seen, you are com- paring GUID values here, so you must use the CONVERT syntax in a search expression, looking for a row that has the required CharacterId and AddressId values: FolktaleDBDataSet.CharacterAddressRow[] characterAddressRow = data.CharacterAddress.Select( “CharacterId = CONVERT(‘{“ + characterRow.CharacterId.ToString() + “}‘, ‘System.Guid’)“ + “ AND “ + “AddressId = CONVERT(‘{“ + addressRow.AddressId.ToString() + “}‘, ‘System.Guid’)“) as FolktaleDBDataSet.CharacterAddressRow[]; You delete the row you find by calling its Delete() method: characterAddressRow[0].Delete(); With the CharacterAddress row deleted, the next step is to see whether the Address row can be deleted. To do so, you use CharacterAddress.Select() again, but this time just look to see if any rows reference the Address row: characterAddressRow = data.CharacterAddress.Select( “AddressId = CONVERT(‘{“ + addressRow.AddressId.ToString() + “}‘, ‘System.Guid’)“) as FolktaleDBDataSet.CharacterAddressRow[]; If no rows are obtained, the address can be deleted: if (characterAddressRow.Length == 0) { addressRow.Delete(); } 399 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 399 Finally, the address view is refreshed to take into account the deleted entry: // Refilter view. SetAddressFilter(); } The other method you added is the event handler for the Save Changes button. It uses the FolktaleDBDataSet.GetChanges() method to obtain a data set containing just the changes that have been made, if any, or a null value. Any changes are updated to the database through the web service Update() web method. private void saveButton_Click(object sender, EventArgs e) { FolktaleDBDataSet changes = data.GetChanges() as FolktaleDBDataSet; if (changes != null) { service.UpdateData(changes); data.AcceptChanges(); } } Doing the update this way minimizes the amount of data that is sent to the web service — there’s no need to send the entire data set. You have looked through quite a lot of code in this example, but the end result is a fluid, responsive application that allows easy data access in a distributed environment. It is well worth learning these techniques because they’re all common ones that you will more than likely find yourself using. One final point is that the application doesn’t take concurrency issues into account. As users of the client application are likely to spend some time modifying data before saving changes, it is possible that changes will fail to commit on the server. If that’s the case, the web service will raise a DBConcurrencyException as you saw in the previous chapter, and you can deal with it the same way. Because you’re using bulk updates here, it’s probably a better idea to record all of the failed updates before raising an exception that the client can handle, to avoid excessive round trips to the server. That modification, however, is left as an exercise for you. Caching Web Service Data One of the main differences between Windows applications and web applications is that, as discussed in Chapter 5, web applications tend to deal with multiple users simultaneously. This applies to web service applications as much as it does web site applications. In both cases, particularly when clients use the web application primarily to read data, you have the opportunity to improve performance by caching data in the web application. That means the web appli- cation keeps a local copy of database data in memory, which is shared by all users, so that, in most cases, user requests can be handled without the web application having to read data from the database. This reduces network traffic and the load on the database server, as well as making things faster for clients. 400 Chapter 10 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 400 There are, however, certain things of which you must be aware when implementing caching in a web application. First, and perhaps most important, is that the database may be modified by other applica- tions, and those modifications will not be reflected in the web application’s cached data unless the cache is refreshed. That means that you either have to refresh the cached data periodically, or include a mecha- nism for detecting database changes and refresh the data accordingly. Lucky for us, both of these tech- niques are relatively easy to implement. Another point to bear in mind is that changes to the cached data should be committed to the database as soon as possible to avoid data inconsistency and concurrency errors. In many situations, the web appli- cation is only used to read data, or perhaps read much more often than modified, so this won’t be a huge problem. Sometimes, however, you have to use the concurrency and transaction techniques you learned in Chapter 9 in combination with frequent commits of cached data to the database. If that becomes an issue, you can always use the simpler technique of not worrying about caching. The choice, as ever, is up to you and the requirements of your application. This section explains how you can cache data, where you can store cached data, and how you can ensure that your cached data stays as up-to-date as possible. Web Method Output Caching The simplest method of caching data is to use output caching. In a web application you can use output caching so that a web page returns the same HTML for a given period of time. That means the first time the page is accessed, the code is processed in the usual way and the output HTML is generated. The HTML is returned to the client, and it is also cached (stored). Subsequent requests bypass ASP.NET pro- cessing and simply obtain the cached HTML for the page, greatly improving performance. This happens until the cache expires, at which point the next request causes the page to be re-processed, and cached again. The same thing is possible for web methods. You can set an output cache to store the result of a web method, and subsequent requests (which use matching parameters, if the web method has any parame- ters) receive the cached result. Configuring web method caching is the work of moments. Simply set the CacheDuration property of the WebMethod attribute used in the method declaration to the number of seconds to cache output for (the default value is 0). For example: [WebMethod(CacheDuration=60)] public FolktaleDBDataSet GetData() { } This causes the GetData() web method to cache its output for 60 seconds at a time. The problem is that modifications to the database are not reflected in the cached data until it refreshes. This means that a client could get data, modify it, get it again, and not see the modifications that it made seconds before. Eventually changes are reflected, but this can lead to concurrency problems. 401 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 401 Still, if you are prepared to deal with the consequences, this is a highly efficient way of doing things because much of the time no code execution is required to obtain web method results. Caching Data in Application State When a web application is first accessed, it’s compiled and loaded into the ASP.NET process. This hap- pens only once — subsequent accesses use the already compiled application, and there’s only one appli- cation that serves all subsequent requests. That is the case until the application is unloaded in one of the following ways: ❑ Manually ❑ Automatically as a result of code file changes ❑ When the hosting server restarts ❑ When the application times out (if a timeout setting has been applied) During the lifetime of the web application you can, if you want, store shared data in what is known as application state. That state is accessible from all requests for all users, making it the ideal place to store database data and other shared information. You use the HttpApplication object, which is generated when an ASP.NET application is loaded, to access web application state. That object is accessible using the Page.Application, WebService .Application , and HttpContext.Current.Application properties, to name but a few. Generally, whenever you are writing code for a web application, the HttpApplication object is easily accessible. You can use the HttpApplication.Add() method to add information to application state. It takes a string key and an object value as its parameters, meaning that you can store any serializable object this way, with a string identifier. Once stored, you can access and manipulate the data using standard collec- tion syntax. To obtain a value stored with the key MyData, for example, you can just use the syntax Application[“MyData”]. The HttpApplication class also includes a Remove() method to remove data, a Count property to see how many items are stored, and so on. Crucially, the HttpApplication class defines Lock() and Unlock() methods. If you call Lock(), no requests other than the current one can read or modify application data until you call Unlock(). If you are modifying application state, place your modification code between Lock() and Unlock() calls to prevent concurrency problems (yes, they exist here, too). That allows you to control how cached data is refreshed — if a client updates data through a web service, you can manually update the cached data, ensuring that subsequent requests for data have an up-to-date version of the data. This is an improve- ment over output caching, described in the previous section, although performance is not so good. The best time to cache data in application state is when the application loads. That does not — repeat, not — mean that you should do this in the web form constructor. There is a much better place: the global application class for the application. This class isn’t included in web applications by default, but you can add it by right-clicking on your project, selecting New Item, and then selecting it from the list of Visual Studio Installed Templates, as shown in Figure 10-11. 402 Chapter 10 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 402 Figure 10-11: Adding a global application class to a web application The generated file, global.asax, contains code as follows: <%@ Application Language=”C#“ %> <script runat=”server”> void Application_Start(object sender, EventArgs e) { // Code that runs on application startup } void Application_End(object sender, EventArgs e) { // Code that runs on application shutdown } void Application_Error(object sender, EventArgs e) { // Code that runs when an unhandled error occurs } void Session_Start(object sender, EventArgs e) { // Code that runs when a new session is started } void Session_End(object sender, EventArgs e) { // Code that runs when a session ends. // Note: The Session_End event is raised only when the sessionstate mode // is set to InProc in the Web.config file. If session mode is set to 403 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 403 // StateServer or SQLServer, the event is not raised. } </script> The comments here are fairly self-explanatory. The purpose of the file is to enable you to execute code at various points in the lifecycle of a web application, and the important thing for the purposes of this dis- cussion is the Application_Start() method. It is executed only once — when the web application starts — which means that it’s the ideal place to load database data into application storage. Here’s an example: void Application_Start(object sender, EventArgs e) { // Load data. FolktaleDBDataSet data = new FolktaleDBDataSet(); FolktaleDBDataSetTableAdapters.CharacterTableAdapter adapter = new FolktaleDBDataSetTableAdapters.CharacterTableAdapter(); adapter.Fill(data.Character); // Store data Application.Add(“cachedData”, data); } Then, code elsewhere in the application can get the data as follows: FolktaleDBDataSet data = Application[“cachedData”] as FolktaleDBDataSet; Remember to have your code lock and unlock application state when modifying this cached store. Caching Data in the Application Cache Application state may appear to be ideal for storing database data, but in fact there is something even better: the application cache. Using the application cache is similar to using application state, but the storage is more specialized. The major difference is that objects stored in the application cache are volatile, meaning that they are not stored for the lifetime of the application. Instead, you can configure items to expire after a set period of time, which can be a great advantage in streamlining the resource usage of your applications. However, the fact that this storage is volatile means that you should always check for the presence of a saved object before attempting to retrieve it, and re-create it if necessary. Another advantage of the application cache is that you can configure items to expire on other conditions, including database changes. That’s extremely useful because rather than polling the database periodi- cally to check for changes, you can find out automatically. You can configure dependencies on any num- ber of tables so that any changes to them result in the cache item expiring. You can access the application cache programmatically in a similar way to application state, using HttpContext.Current.Cache from web service code, or the Page.Cache property in web site applica- tions. Sometimes in web site applications, as you will see shortly, you can configure data caching in the application cache without needing to do this. For web services, however, you will need to manipulate the application cache manually. Let’s take a look at how you can configure and use the application cache for database data. 404 Chapter 10 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 404 Enabling Database Dependency To respond to database changes, you must first configure the database to supply notifications to you. You must also configure any tables for which you want to be notified of changes. There are two ways to do so: ❑ Using the aspnet_regsql.exe command line tool ❑ Programmatically The command line tool is located in the following directory: <WindowsDirectory>\Microsoft.NET\Framework\<Version>\aspnet_regsql.exe Run it with the command line parameter -? to find instructions on how to use the tool. You can connect to a database either by supplying a connection string as the -C parameter, or by providing the server name using -S, authentication details using -U and -P for username and password, or -E for integrated security. Then you give the database name using -d and enable SQL cache dependency using -ed. You then need to run the tool again, specifying that you want to enable a database table using -et, and speci- fying the table name using -t. The following two commands enable the Wish table in the FolktaleDB database for SQL cache dependency: aspnet_regsql -S .\SQLEXPRESS -d FolktaleDB -E -ed aspnet_regsql -S .\SQLEXPRESS -et -t Wish -d FolktaleDB –E You can also run the tool with the -W parameter, or with no parameters, and configure a database using the ASP.NET SQL Server Setup Wizard, shown in Figure 10-12. Unfortunately, the command line tool doesn’t give you access to all the options, such as configuring individual tables. Also, the tool (in command line or wizard mode) cannot configure local database files. Figure 10-12: Using the aspnet_regsql.exe wizard 405 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 405 Programmatic configuration is often the easier option — and works with local database files. You use the SqlCacheDependencyAdmin class, which is located in the System.Web.Caching name- space. This class includes two static methods that you can use to configure a database. The first, EnableNotifications(), enables a database for notifications. You supply it with a connection string for a database, which you can take from the connection strings stored in web.config if you want: string connectionString = ConfigurationManager.ConnectionStrings[“ConnectionString”].ConnectionString; SqlCacheDependencyAdmin.EnableNotifications(connectionString); The other method is EnableTableForNotifications(). It enables individual tables and requires a connection string and a table name as follows: SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, “Wish”); You can also check to see if a specific table is enabled by getting a list of enabled tables using the GetTablesEnabledForNotifications() method. If you try this and the database isn’t enabled for notifications, you receive a DatabaseNotEnabledForNotificationException exception. Attempting to enable a database or table more than once won’t cause an error, but you can use the fol- lowing code to test whether a database and table are enabled, and then you can enable them if not: string connectionString = ConfigurationManager.ConnectionStrings[“ConnectionString”].ConnectionString; try { string[] tables = SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(connectionString); bool tableEnabled = false; foreach (string table in tables) { if (table == “Wish”) { tableEnabled = true; break; } } if (!tableEnabled) { SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, “Wish”); } } catch (DatabaseNotEnabledForNotificationException) { SqlCacheDependencyAdmin.EnableNotifications(connectionString); SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, “Wish”); } You can disable these notifications from databases and tables by using two other methods of the SqlCacheDependencyAdmin class, DisableNotifications() and DisableTableForNotifications(), should you want to. 406 Chapter 10 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 406 SQL Dependency Configuration The next step in configuring SQL dependencies for the application cache is to add a small amount of configuration code to the web.config file. In it you can configure a few specifics for use in cache dependency checking, and in many cases that’s enough to configure data access without using any addi- tional code in your applications. Once configured, you can reference the configuration using declarative syntax in your ASP.NET pages, as you will see in the next section. Caching settings are configured in the <caching> element in web.config files, which is a child of the <system.web> element. This element isn’t added by default, so you need to add it yourself: <system.web> <caching> </caching> </system.web> You can add several settings here, including SQL dependency, which is the only one you’ll look at here. It is configured using the <sqlCacheDependency> element. This element has two attributes: ❑ enabled: Set to true to enable dependency checking. ❑ pollTime: Time, specified in milliseconds, that determines how often the database should be polled for changes. The default value is 1 minute, and you cannot set it to a value of less than 500 milliseconds. This attribute is optional. Here’s an example of setting these attributes: <caching> <sqlCacheDependency enabled=”true” pollTime=”1000”> </sqlCacheDependency> </caching> Within the <sqlCacheDependency> element, you must include a <databases> element (which has no attributes). It defines one or more databases to poll for changes using child <add> elements. Each <add> element has a name defined by a name attribute, which is used to identify the SQL dependency in declar- ative and programmatic code. <add> elements also require a connection string identifying the database to poll, specified using the connectionStringName attribute. This attribute refers to a connection string defined elsewhere in the web.config file. Here’s an example: <caching> <sqlCacheDependency enabled=”true” pollTime=”1000”> <databases> <add name=”FolktaleDB” connectionStringName=”ConnectionString” /> </databases> </sqlCacheDependency> </caching> This configuration does not include table names, which are specified in your code depending on what tables you need to access. 407 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/12/06 10:43 PM Page 407 [...]... FUNCTION dbo.GetHash DROP ASSEMBLY Ex1101 GO 4 29 44063c11.qxd:WroxBeg 9/ 12/06 10:45 PM Page 430 Chapter 11 10 Save the query as CallFunction.sql in the C:\BegVC #Databases\ Chapter11\Ex1101 Scalar Functions directory 11 Execute the query The result is shown in Figure 11-4 Figure 11-4: Query result 12 Close SQL Server Management Studio Express and Visual C# Express How It Works In this example, you create... application cache, and test the functionality Try It Out Cached Web Service Data 1 Copy the web service application directory created earlier in the chapter (C:\BegVC #Databases\ Chapter10\Ex1002 - WS Data Access) to a new directory, C:\BegVC #Databases\ Chapter10\Ex1004 - Caching Data Open Visual Web Developer and open the application from its new location 2 3 Add a Global Application Class, Global.asax,... Expand the Databases folder, right-click the FolktaleDB database, and then select New Query Add the following SQL code: ALTER DATABASE FolktaleDB SET TRUSTWORTHY ON EXEC sp_changedbowner ‘sa’ 3 4 5 Execute the SQL statement Close the SQL Query and discard changes Open Visual C# Express and create a new class library project called Ex1102 - Table Functions Save the project in the C:\BegVC #Databases\ Chapter11... have to do to enable the use of managed C# code in SQL Server, as well as how to actually place code there and use it You’ll also look at creating and using functions and stored procedures written in C#, including scalar, table-valued, and aggregate functions Briefly, you will learn: ❑ To enable CLR integration ❑ To add assemblies to SQL Server 44063c11.qxd:WroxBeg 9/ 12/06 10:45 PM Page 418 Chapter 11... statement must be a member of the sysadmin role in SQL Server 420 44063c11.qxd:WroxBeg 9/ 12/06 10:45 PM Page 421 SQL Server CLR Integration Assemblies loaded this way can be seen in the Assemblies folder in the Database Explorer window in Visual C# Express, as shown in Figure 11-2 Figure 11-2: Viewing loaded assemblies in Visual C# Express In SQL Server Management Studio Express, the assembly is found in the... Specifically, you will use Visual C# Express to create assemblies for CLR integration, and use SQL Management Studio Express to load and test the assemblies SQL Server Management Studio Express makes it easier to write and save the queries that you will require Should you want to test your modified databases from client applications, you may have to detach/reattach databases (as described in Appendix... following SQL code: EXEC sp_configure ‘clr enabled’, 1 RECONFIGURE 4 5 6 Execute the SQL statement Close the SQL Query and discard changes Open Visual C# Express and create a new class library project called Ex1101 - Scalar Functions Save the project in the C:\BegVC #Databases\ Chapter11 directory, with the Create Directory For Solution option unchecked 7 Remove the auto-generated Class1.cs file, add a new class... output = output.Substring(0, output.Length - 1); // Return output bytes as string return output; } } 8 9 Compile the project Return to SQL Server Management Studio and create another new query for the FolktaleDB database Add SQL code as follows: USE FolktaleDB GO CREATE ASSEMBLY Ex1101 FROM ‘C:\BegVC #Databases\ Chapter11\Ex1101 - Scalar Functions\bin\Release\ Ex1101 - Scalar Functions.dll’ WITH PERMISSION_SET... request is received for the page When cached data expires, it is refreshed automatically the next time the control is required to supply data for data binding or from your C# code 408 44063c10.qxd:WroxBeg 9/ 12/06 10:43 PM Page 4 09 Working with Disconnected Data Because SqlDataSource controls won’t be used in web service code, and in some situations in web pages, you also need to know how to use cache... SqlDataSource controls, and how would you do it programmatically? 415 44063c10.qxd:WroxBeg 9/ 12/06 10:43 PM Page 416 44063c11.qxd:WroxBeg 9/ 12/06 10:45 PM Page 417 11 SQL Ser ver CLR Integration Integrating CLR (Common Language Runtime) code in SQL Server, a functionality that was introduced with NET 2.0 and SQL Server 2005, enables you to write NET code that runs inside SQL Server (or SQL Server Express) . (characterAddressRow.Length == 0) { addressRow.Delete(); } 399 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/ 12/06 10:43 PM Page 399 Finally, the address view is refreshed to take into account. application directory created earlier in the chapter (C:BegVC #Databases Chapter10Ex1002 - WS Data Access ) to a new directory, C:BegVC #Databases Chapter10Ex1004 - Caching Data . Open Visual. CacheItemPriority, which determines how likely the cached item is to be 4 09 Working with Disconnected Data 44063c10.qxd:WroxBeg 9/ 12/06 10:43 PM Page 4 09 removed if .NET decides to clean up resources. You can