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 related configuration information). The program could use this information to construct a tailored instance definition with the correct number of delivery channel declarations. Without NMO, implementing the equivalent functionality would require writing custom XML processing code that pieces together an ICF from a static template. NMO provides a much simpler programming interface for dynamic instance definition.

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 Create() method on the Instance object to compile the instance. This invokes the same compilation operations carried out by the standard instance creation tools, nscontrol create and the New Notification Services Instance command in Management Studio.

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

 public class StockBrokerNMO {    ...    private Server server;    ...    public StockBrokerNMO(        string sqlServer,        AuthenticationMode authenticationMode,        string sqlUserName,        string sqlPassword,        string baseDirectoryPath,        string serviceUserName,        string servicePassword,        AuthenticationMode serviceAuthenticationMode,        string serviceSqlUserName,        string serviceSqlPassword)     {        ...        // Establish a server connection.        ServerConnection serverConnection = new ServerConnection();        serverConnection.ServerInstance = sqlServer;        if (AuthenticationMode.SqlServerAuthentication ==            authenticationMode)        {           serverConnection.LoginSecure = false;           serverConnection.Login = sqlUserName;           serverConnection.Password = sqlPassword;        }        server = new Server(serverConnection);     }     ...  } 

The constructor creates a ServerConnection object which represents a SQL Server connection. ServerConnection is a class in the SMO library; it provides various properties that define a connection to a SQL Server. Specifically, the ServerInstance property specifies the SQL Server instance name; the LoginSecure, Login, and Password properties control the authentication method used to connect. When LoginSecure is false, and the Login and Password properties specify a SQL Server username and password, the connection uses SQL Server Authentication. Otherwise, Windows Authentication is used. The code in Listing 16.3 sets these properties based on the values of the constructor's parameters.

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.

The Server object created in the StockBrokerNMO constructor is stored in a private class member variable so that it can be used in other methods. For example, the Create() method uses it, as shown in Listing 16.4.

Listing 16.4. The Create() Method of the StockBrokerNMO Class

 public class StockBrokerNMO {    ...    const string InstanceName ="StockBrokerNMO";    private Server server;    private Instance instance;    ...    public void Create()    {        // Create a new instance object.        instance = new Instance(            server.NotificationServices,            InstanceName);        // Add the instance to the server's collection.        server.NotificationServices.Instances.Add(instance);        // Specify the instance database and schema names.        instance.DatabaseName ="StockBrokerNMO";        instance.SchemaName ="NSInstance";        // Define the delivery channel.        DeliveryChannel dc = new DeliveryChannel(            instance,            "FileChannel");        dc.ProtocolName ="File";        instance.DeliveryChannels.Add(dc);        // Add the delivery channel arguments.        DeliveryChannelArgument dcArg =            new DeliveryChannelArgument(dc, "FileName");        dcArg.Value = Path.Combine(            baseDirectoryPath,            "FileNotifications.txt");        dc.DeliveryChannelArguments.Add(dcArg);        // Define the StockWatcher application and add it        // to the instance.        Application stockWatcherApplication =            BuildStockWatcherApplication();        instance.Applications.Add(stockWatcherApplication);        // Create the instance.        instance.Create();    }    ... } 

The code in the Create() method creates an NMO Instance object to represent the StockBrokerNMO SQL-NS instance. It fills in the properties of the Instance object, and then creates all the child objects needed to fully define the instance. Among these child objects is an Application object that represents the instance's StockWatcher application. This Application object is created and populated by a helper method, BuildStockWatcherApplication(), that we examine in the following section, "Defining an Application with NMO" (p. 543).

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 Create() method shown in Listing 16.4 creates the new NMO Instance object. The Instance constructor takes two arguments: the instance's parent NotificationServices object and the instance name. Here, the parent is specified as the NotificationServices property of the Server object created earlier and stored in the private member variable, server. The instance name is obtained from the string constant, InstanceName, defined at the top of the class. The newly created Instance object is stored in the StockBrokerNMO class's private member variable, instance. This makes it accessible to other methods in the class, as you'll see in the subsequent sections of this chapter.

Caution

The SMO Server class supports old versions of SQL Server. For example, a Server object can represent an instance of SQL Server 2000 or SQL Server 7.0. Although the Server class and other parts of SMO support releases of SQL Server prior to 2005, NMO does not. Accessing the NotificationServices property on a Server object that represents an old SQL Server (SQL Server 2000 or earlier) results in an exception of type InvalidSmoOperationException.


After the Instance object is created, it is added to the parent object's Instances 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 constructed, and the child object is then explicitly added to the appropriate collection in the parent object. Both steps are necessary to complete the linkage. This pattern is used throughout the code that creates the NMO object hierarchy to define the instance. In cases where a parent object can have only one instance of a particular type of child object (for example, a notification class may have only one content formatter), the child object is set as a property on the parent object, rather than added to a collection.

Having created the Instance object and added it to the parent object's Instances collection, the code in Listing 16.4 goes on to specify the instance's properties. For example, the next two lines specify the instance database name and schema name. Then the various child objects are created. In this example, only two child objects are needed: a delivery channel and an application.

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 DeliveryChannelArgument object is created, initialized, and then added to the parent's DeliveryChannelArguments collection. In this case, the parent is the delivery channel itself.

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.

Tip

To see how the instance definition specified here in code compares with the XML definitions used in Chapter 3, refer to the files C:\SQL-NS\Samples\StockBroker\InstanceConfiguration.xml and C:\SQL-NS\Samples\StockBroker\StockWatcher\ApplicationDefinition.xml. As you'll see by comparing with the contents of these files, the code used here specifies essentially the same information as the XML definitions.


Note

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

 public class StockBrokerNMO {    ...    private Application BuildStockWatcherApplication()    {        // Create the application object.        Application application = new Application(            instance,            "StockWatcher");       // We don't add the application to the instance's       // applications collection here - that's done outside      // this method.     // Set the database and schema names.    application.DatabaseName = "StockBrokerNMO";    application.SchemaName = "StockWatcher";    // Set the base directory path.    application.BaseDirectoryPath = Path.Combine(        baseDirectoryPath,        application.Name);    // Add the event class.    EventClass ec = new EventClass(         application,         "StockPriceChange");    application.EventClasses.Add(ec);    // Add the event fields to the event class.    EventField ef = new EventField(ec, "StockSymbol");    ef.Type = "NVARCHAR(10)";     ef.TypeModifier = "NOT NULL";     ec.EventFields.Add(ef);     ef = new EventField(ec, "StockPrice");     ef.Type = "SMALLMONEY";     ef.TypeModifier = "NOT NULL";     ec.EventFields.Add(ef);     // Add the subscription class.     SubscriptionClass sc = new SubscriptionClass(         application,         "StockPriceHitsTarget");     application.SubscriptionClasses.Add(sc);     // Add the subscription fields.     SubscriptionField sf = new SubscriptionField(         sc,         "StockSymbol");     sf.Type = "NVARCHAR(10)";     sf.TypeModifier = "NOT NULL";     sc.SubscriptionFields.Add(sf);     sf = new SubscriptionField(sc, "StockPriceTarget");     sf.Type = "SMALLMONEY";     sf.TypeModifier = "NOT NULL";     sc.SubscriptionFields.Add(sf);     // Add the event rule.     SubscriptionEventRule rule = new SubscriptionEventRule(        sc, "MatchStockPricesWithTargets");     rule.Action = @"     INSERT INTO [StockWatcher].[StockAlert]     SELECT subscriptions.SubscriberId,            N'DefaultDevice',            N'en-US',            events.StockSymbol,            events.StockPrice,            subscriptions.StockPriceTarget     FROM   [StockWatcher].[StockPriceChange] events     JOIN   [StockWatcher].[StockPriceHitsTarget] subscriptions         ON events.StockSymbol = subscriptions.StockSymbol     WHERE events.StockPrice >= subscriptions.StockPriceTarget";     rule.EventClassName = ec.Name;     sc.SubscriptionEventRules.Add(rule);     // Add the notification class.     NotificationClass nc = new NotificationClass(         application,         "StockAlert");     application.NotificationClasses.Add(nc);     // Add the notification fields.     NotificationField nf = new NotificationField(         nc,         "StockSymbol");     nf.Type = "NVARCHAR(10)";     nc.NotificationFields.Add(nf);     nf = new NotificationField(nc, "StockPrice");     nf.Type = "SMALLMONEY";     nc.NotificationFields.Add(nf);     nf = new NotificationField(nc, "StockPriceTarget");     nf.Type = "SMALLMONEY";     nc.NotificationFields.Add(nf);     // Define the content formatter and its arguments.     nc.ContentFormatter = new ContentFormatter(          nc,          "XsltFormatter");     ContentFormatterArgument cfArg =         new ContentFormatterArgument(             nc.ContentFormatter,             "XsltBaseDirectoryPath");     cfArg.Value = Path.Combine(         application.BaseDirectoryPath,         "XslTransforms");     nc.ContentFormatter.ContentFormatterArguments.Add(cfArg);     cfArg = new ContentFormatterArgument(         nc.ContentFormatter, "XsltFileName");     cfArg.Value = "StockAlert.xslt";     nc.ContentFormatter.ContentFormatterArguments.Add(cfArg);     // Add the protocols for the notification class.     NotificationClassProtocol protocol =         new NotificationClassProtocol(nc, "File");     nc.NotificationClassProtocols.Add(protocol);     // Add the nonhosted event provider.     NonHostedEventProvider provider =        new NonHostedEventProvider(        application,        "TestEventProvider");     application.NonHostedEventProviders.Add(provider);     // Define the generator.     application.Generator = new Generator(         application,         "Generator");     application.Generator.SystemName = Environment.MachineName;     Distributor distributor = new Distributor(         application,         "Distributor1");     distributor.SystemName = Environment.MachineName;     distributor.QuantumDuration = new TimeSpan(0, 0, 15);     application.Distributors.Add(distributor);     // Specify the application's execution settings.     application.QuantumDuration = new TimeSpan(0, 0, 15);     return application;   }   ... } 

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 follows essentially the same pattern as the event and subscription classes. But the notification class also specifies a content formatter and a protocol.

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.

Note

The ContentFormatter object does not have an explicit ClassName property, as you might expect. Instead, the content formatter class name (XsltFormatter, in this example) is specified as the ContentFormatter object's name. In general, NMO classes and their properties mirror the structure (and naming conventions) of their counterpart ADF elements, but this is one exception.


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.

The StockWatcher application configures three running components: a standalone event provider, a generator, and a distributor. These are represented by NonHostedEventProvider, Generator, and Distributor objects, respectively. Like the ContentFormatter object in the NotificationClass object, the Generator child object is a singleton in the parent Application object. This reflects the restriction that each application has only one generator. As you can see from Listing 16.5, the Generator object is created and assigned to the Application object's Generator property, rather than added to a collection.

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).

Note

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.




Microsoft SQL Server 2005 Notification Services
Microsoft SQL Server 2005 Notification Services
ISBN: 0672327791
EAN: 2147483647
Year: 2006
Pages: 166
Authors: Shyam Pather

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net