Caching Data in the Data Island
When a customized document with data-bound controls starts up, the datasets have to be filled in somehow before the controls display the data. As you saw at the beginning of this chapter, if you use the Data Sources pane to create data-bound controls, Visual Studio automatically emits code to fill the datasets using custom-generated adapters:
private void ThisDocument_Startup(object sender, System.EventArgs e){ if (this.NeedsFill("northwindDataSet")) this.productsTableAdapter.Fill(this.northwindDataSet.Products);
But under what circumstances would the dataset ever not need to be filled at startup? Consider, for example, a spreadsheet with a dataset containing a single table. One worksheet has a single datum bound to a named range. If you save that spreadsheet, only that one datum is going to be saved; all the other information in the dataset is just a structure in memory at runtime that will be lost when the workbook is closed. The data is potentially going to have to be fetched anew every time the worksheet host control starts up.
One of the key benefits of Word and Excel documents is that they are useful even on machines that are not connected to networks. (Working on a spreadsheet or document on a laptop on an airplane is the canonical scenario.) It would be unfortunate indeed if a data-bound customized document required your users to always be connected.
Fortunately, VSTO solves this problem. Click the icon for the typed dataset in the component tray, and then look at the Properties pane for this component. A Cached property defaults to false. If you set it to TRue, when you save the document the VSTO runtime will turn the dataset into XML and store the XML in a "data island" inside the document.
The next time the document starts up, the VSTO runtime detects that the data island contains a cached dataset and fills in the dataset from the cache. The call to NeedsFill in the Startup event will then return false, and the startup code will not attempt to fill in the data from the adapter. Essentially, the NeedsFill method returns false if the object was loaded from the cache automatically, true otherwise.
Caching Your Own Data Types
You can cache almost any kind of data in the XML data island, not just datasets. To be cacheable by the VSTO runtime, you must meet the following criteria:
- The data must be stored in a public member variable or property of a host item (a customized worksheet, workbook, chart sheet, or document class).
- If stored in a property, the property must have no parameters and be both readable and writable.
- The runtime type of the data must be either dataset (or a subclass), data table (or a subclass), or any type serializable by the System.Xml.Serialization. XmlSerializer object.
To tell Visual Studio that you would like to cache a member variable, just add the Cached attribute to its declaration. Make sure you check whether the member was already filled in from the cache; the first time the document is run there will be no data in the cache, so you have to fill in the data somehow. For example, you could use the code in Listing 17-5.
Listing 17-5. Auto-Generated Table-Filling Code
public partial class ThisDocument { [Cached] public string CustomerName; private void ThisDocument_Startup(object sender, System.EventArgs e) { if (this.NeedsFill("CustomerName")) this.CustomerName = "Unknown Customer" } }
Dynamically Adding and Removing Cached Members from the Data Island
Cached data can be large; what if you decide that at some point you want to stop caching a particular dataset in the data island? Or, conversely, what if you do not want to automatically fill in a dataset and store it in the cache on the first run of the document, but rather want to start caching a member based on some other criterion? It would be unfortunate if the only way to tell VSTO to cache a member in the data island was to tag it with the Cached attribute at design time.
Therefore, all customized view item classes generated by a VSTO project expose four handy functions that you can call to query and manipulate the caching semantics, as follows:
- bool NeedsFill(string memberName)
- bool IsCached(string memberName)
- void StartCaching(string memberName)
- void StopCaching(string memberName)
NeedsFill we have already seen; if the named member was initialized from the data island by the VSTO runtime when the customization started up, this returns false. Otherwise, it returns TRue.
IsCached might seem like it is just the opposite of NeedsFill, but it is not. NeedsFill tells you whether the item in question was loaded out of the data island; IsCached tells you whether the item will be saved to the data island when the user saves the document.
StartCaching and StopCaching dynamically add and remove members from the set of members that will be saved to the data island. It is illegal to call StartCaching on a member already in the cache or StopCaching on a member not in the cache; use IsCached to double-check if you need to. The same rules that apply to cached members added to the cache by the Cached attribute apply to dynamically added members; only call StartCaching on public fields or public readable/writable properties.
If a cached member is set to null at the time that the document is saved, the VSTO runtime assumes that you intended to call StopCaching on the member and it will be removed from the data island. |
Advanced Topic: Using ICachedType
Suppose you have a large cached dataset that you loaded out of the data island when the customization started up. Serializing a dataset into XML can be a time- and memory-consuming process; so if there have been no changes to the dataset when the document is saved, the VSTO runtime is pretty smart about skipping the serialization.
This is also important if the user closes Word or Excel without saving the document. The host application needs to know whether to create the "Do you want to save changes?" dialog box. If the dataset is clean, there are no changes to save and the dialog should not be created.
How can VSTO tell whether a custom class added to the cached members is dirty? The VSTO runtime can track the Change events on a dataset or data table to tell whether they are dirty, but in general, any other types simply have to be written out every time. To prevent the "Do you want to save?" dialog, the VSTO runtime must pessimistically serialize the object and compare it to the state that it loaded; this is again potentially time-consuming.
If you require more finely grained control over the caching process for a particular member, you can implement the ICachedType interface. This interface enables you to not only hint to the VSTO runtime whether the item needs to be re-serialized, it also allows you to dynamically abort a save or load, and receive notification when the save or load is done. Listing 17-6 shows its members.
Listing 17-6. The ICachedType Inteface
namespace Microsoft.VisualStudio.Tools.Applications.Runtime { public interface ICachedType { bool IsDirty { get; } bool BeforeLoad(); void AfterLoad(); bool BeforeSave(); void AfterSave(); } }
If you implement this interface on a particular class and then add a member containing an instance to the class, the VSTO runtime will do the following:
- Call your BeforeLoad method when the item is loaded out of the cache. If you return false, the load will be aborted.
- Call your AfterLoad method when the XMLSerializer is done loading your object. (If you are tracking the dirty state of the object, this would be a good time to set it to clean.)
- Call IsDirty before saving the document; if the object has no changes since it was last loaded or saved, return false to avoid unnecessary expensive serializations.
- Call BeforeSave before saving the member to the data island. If for some reason you determine that the object is not in a state that can be saved, you can return false and the object will be removed from the cache.
- Call AfterSave when the XMLSerializer is done saving the document to the data island. (Again, this would be a good time to note that the object is clean.)
Manipulating the Serialized XML Directly
Chapter 18 discusses how to view and edit the contents of the data island, start and stop caching members, and so on, without actually starting Word or Excel.