Using NMO to Define and Create an Instance
The classes in the NMO API can be used to define a new SQL-NS instance programmatically and then compile this definition into the corresponding SQL-NS database objects. Using NMO, you can embed the definition of an instance, as well as the functionality of the SQL-NS compiler, in your own applications.
This capability is useful if you need to construct an instance definition dynamically. For example, imagine a scenario in which some aspects of a SQL-NS instance's deployment configuration, such as the number of delivery channels, aren't known at development time. Constructing a static ICF that declares the delivery channels simply isn't possible.
With NMO, you can instead write a program that builds the instance definition dynamically, based on options specified at deployment time. Among these options could be the number of delivery channels (and their
Defining a SQL-NS instance with the NMO API involves building a hierarchy of NMO objects. Each object represents one entity in the instance, such as a delivery channel or an event class. You create each object of the appropriate NMO class, fill in its properties, and then link it to a parent object. This process begins with an object of the NMO Instance class, which represents a whole instance.
After the NMO object hierarchy that defines the instance is built, you call the
method on the
object to compile the instance. This invokes the same compilation operations carried out by the standard instance creation tools,
This section looks at the code that defines and creates the sample StockBrokerNMO instance. The code uses NMO classes to define the same information that was specified in the ICF and ADF for the StockBroker sample in Chapter 3. The code examples in this section highlight the various programming patterns involved in creating a SQL-NS instance with NMO, but do not cover every class and property. The StockBrokerNMO instance is representative of a simple SQL-NS instance, but does not use all the SQL-NS features. NMO classes dealing with constructs not used in the StockBrokerNMO instance, such as chronicles and scheduled rules, are not covered here. Refer to the SQL-NS Books Online for a complete class reference to the NMO API.
Defining an Instance with NMO
The code that defines the StockBrokerNMO instance is located in the StockBrokerNMO class's Create() method. Before looking at this method, we must examine the StockBrokerNMO class constructor, which establishes the connection to the SQL Server, from which all other operations proceed. The constructor's code is shown in Listing 16.3.
Listing 16.3. The StockBrokerNMO Class Constructor
The constructor creates a
object which represents a SQL Server connection.
is a class in the SMO library; it provides various properties that define a connection to a SQL Server. Specifically, the
property specifies the SQL Server instance
After the ServerConnection object is created, the StockBrokerNMO constructor creates an instance of the Server class. Whereas the ServerConnection object represents a connection to the server, the Server object represents the server itself. The Server constructor takes the ServerConnection object, which it uses to identify and connect to a particular SQL Server. Thereafter, the Server object can be used to manipulate that server and the entities associated with it.
object created in the
constructor is stored in a private class member variable so that it can be used in other
Listing 16.4. The Create() Method of the StockBrokerNMO Class
The code in the
method creates an NMO Instance object to represent the
SQL-NS instance. It fills in the properties of the
object, and then creates all the child objects needed to fully define the instance. Among these child objects is an
object that represents the instance's
object is created and
As shown in Figure 16.1, the parent of an NMO Instance object is always a NotificationServices object attached to a Server object. The NotificationServices object isn't something that needs to be created in application codeit's a singleton that always exists as a property of a Server object.
The first line of the
method shown in Listing 16.4 creates the new NMO
constructor takes two arguments: the instance's parent
object and the instance name. Here, the parent is specified as the
property of the
object created earlier and stored in the private member variable,
. The instance name is obtained from the string constant,
, defined at the top of the class. The newly created
object is stored in the
class's private member variable,
. This makes it accessible to other methods in the class, as you'll see in the
class supports old versions of SQL Server. For example, a
object can represent an instance of SQL Server 2000 or SQL Server 7.0. Although the
class and other
object is created, it is added to the parent object's
collection. This illustrates how a parent-child linkage is established in an NMO object hierarchy: The parent object is specified when the child object is
Having created the
object and added it to the parent object's
collection, the code in Listing 16.4 goes on to specify the instance's properties. For example, the
The DeliveryChannel object is created using a constructor that again takes two arguments: a parent object and a name. Here the delivery channel's parent object is the instance, and the name is given as "FileChannel". The only property we need to set on the DeliveryChannel object, the ProtocolName , is assigned the constant "File" (which identifies the built-in File delivery protocol). The DeliveryChannel object is then added to the DeliveryChannels collection in the parent object (the instance). This establishes the two-way parent-child relationship.
The code in Listing 16.4 then creates a delivery channel argument, following a similar pattern. A
object is created,
As mentioned earlier, the Application object that represents the instance's StockWatcher application is constructed and initialized by a helper method, BuildStockWatcherApplication() , which we examine in the next section. In Listing 16.4, the Application object returned from this method is added to the parent Instance object's Applications collection. This completes the definition of the instance.
The Instance object's Create() method is then called to compile the instance definition and create the corresponding database objects. This method is described in the section "Creating the Instance" (p. 549), after the discussion of the Application object and its children in the next section.
To see how the instance definition specified here in code
NMO provides classes and properties for defining other instance-level entities not used in this chapter's example. Notably, the ProtocolDefinition class encapsulates custom delivery protocol declarations, the InstanceDatabaseOptions class defines the physical storage for instance database objects, and the ArgumentKey property specifies an argument key used to encrypt and decrypt arguments in the instance. You'll find more information on these and other NMO classes in the SQL-NS Books Online.
Defining an Application with NMO
The StockWatcher application in the StockBrokerNMO instance is defined in the BuildStockWatcherApplication() method, as shown in Listing 16.5. The code in this listing uses NMO objects to specify the various parts of the application, including the event, subscription, and notification classes. The application definition produced by this code is essentially the same as that specified in the ADF we examined in Chapter 3.
Listing 16.5. The BuildStockWatcherApplication() Method of the StockBrokerNMO Class
The code begins by creating a new Application object, specifying the Instance object as the application's parent and "StockWatcher" as the application name. Note that the Application object is not added to the parent object's Applications collection here. That is done after BuildStockWatcherApplication() returns, as shown in Listing 16.4.
After creating the Application object, the code fills in the object's properties, including the database name, schema name, and base directory path. It then creates child objects to represent the application's event, subscription, and notification classes, as well as its event provider, generator, and distributor.
The application's event class, StockPriceChange , is represented by an instance of the NMO class, EventClass . As you might expect, the Application object is specified as the EventClass object's parent in the call to its constructor. The EventClass object is added to its parent's EventClasses collection after it is created. As in the examples of the previous section, this establishes the two-way parent-child relationship.
The fields of the event class schema are represented by EventField objects. Properties on the EventField objects specify the field names, types, and type modifiers. The event field objects are children of the EventClass object and are added to its EventFields collection.
The application's subscription class and the fields of its schema are defined in much the same way. A SubscriptionClass object is created, added to the parent application's SubscriptionClasses collection, and child SubscriptionField objects are created for each subscription class field.
The subscription class also contains an event rule. This is represented by a SubscriptionEventRule object. After creating the SubscriptionEventRule object, the code sets its Action and EventClassName properties before adding it to the parent subscription class's SubscriptionEventRules collection. The Action property of the rule object is set to the text of the match rule, almost exactly as it would have been specified in the ADF. Notice, however, that the XML escape sequence, > , is no longer needed in place of the > character, as it was in the ADF; > can be used directly, because the rule text is not part of an XML document.
The definition of the notification class and its schema
The content formatter is represented by an instance of the ContentFormatter class. In this example, the ContentFormatter object specifies the built-in XsltFormatter as its name. Because each notification class can have just a single content formatter, NotificationClass does not have a collection of ContentFormatter objects. Instead, NotificationClass has a single ContentFormatter property, to which the new ContentFormatter object is directly assigned.
Had the ContentFormatter object referred to a custom content formatter class (instead of the built-in XsltFormatter ), its AssemblyName property would have been set. Because XsltFormatter is a built-in formatter class, no assembly name is required, so the code in Listing 16.5 doesn't set the ContentFormatter object's AssemblyName property at all.
object does not have an explicit
property, as you might expect. Instead, the content formatter class name (
, in this example) is specified as the
object's name. In general, NMO classes and their properties mirror the structure (and naming conventions) of their
The two required arguments for the XsltFormatter are represented by two ContentFormatterArgument objects. These are created, initialized with the appropriate values, and then added to the parent ContentFormatter object's ContentFormatterArguments property.
The protocol configuration for the notification class is represented by a single NotificationClassProtocol object. The protocol name, File , is given as the object name and the NotificationClass object is the parent. Because the File protocol configuration in the notification class does not require any protocol fields, nothing else is required before the NotificationClassProtocol object is added to the parent's NotificationClassProtocols collection. Had protocol fields been required, they could have been specified with ProtocolField objects. Also, if the notification class supported more than one protocol, additional NotificationClassProtocol objects could have been created and added to the NotificationClass object's NotificationClassProtocols collection in the same way.
application configures three running
You might be surprised to see that the Generator and Distributor objects in Listing 16.5 are assigned names ("Generator" and "Distributor1", respectively). This is a deviation from the ADF structure, in which <Generator> and <Distributor> declarations do not specify names. The SMO architecture requires that all objects be named and because NMO is part of SMO, this requirement extends to the NMO objects as well. When assigning names for Generator and Distributor objects, you may choose any value that complies with the rules for valid SQL Server identifiers. (See the "Identifiers [SQL Server]" topic in SQL Server Books Online for more details.) If an application has more than one distributor, each Distributor object must be assigned a unique name.
The application execution settings that can be specified in the <ApplicationExecutionSettings> ADF element are specified via properties on the Application object in NMO. NMO does not define a distinct ApplicationExecutionSettings class. In the example in Listing 16.5, the only execution setting specified is the generator's quantum duration. This is set via the Application object's QuantumDuration property.
The quantum duration value is represented as a TimeSpan object. In general, NMO uses TimeSpan objects to represent values that are specified in the XSD duration syntax in the ADF. Looking back a few lines in Listing 16.5, you'll notice that the distributor's quantum duration is also specified with a TimeSpan object, in the Distributor object's QuantumDuration property.
By the end of the BuildStockWatcherApplication() method, the Application object and its children have been completely populated. This completes the application definition. The Application object is returned to the caller (the StockBrokerNMO.Create() method). There it is added to the parent Instance object's Applications collection, as shown in Listing 16.4.
Creating the Instance
An instance defined through NMO can be compiled by invoking the Create() method on the Instance object. In the StockBrokerNMO example, you saw the code that invoked the Instance object's Create() method toward the end of Listing 16.4.
The Instance.Create() method invokes the SQL-NS compiler to validate and then compile the instance definition represented by the Instance object and all its children. If the instance definition is valid, the compiler creates the instance and application database objects on the SQL Server to which the Instance object is connected (the Server object whose NotificationServices property is the Instance object's parent).
As you saw in previous chapters when you invoked the SQL-NS compiler using the standard SQL-NS tools ( nscontrol create and the New Notification Services Instance command in Management Studio), compiling a SQL-NS instance can take several minutes. The Instance.Create() method may well take a similarly long time to complete.
If the compiler encounters an error as it creates the instance, the Instance.Create() method throws an exception. The exception object thrown indicates that the creation process failed. The inner exception object (accessed via the exception's InnerException property) usually provides more details as to the cause of the failure.