Using Replication with SQL Server CE

Team-Fly    

 
eMbedded Visual Basic: Windows CE and Pocket PC Mobile Applications
By Chris Tacke, Timothy Bassett
Table of Contents
Chapter 8.  Using SQL Server in a Mobile Environment


The following sections discuss the process to convert the CD Library application from Chapter 7 into an enterprise solution that uses SQL Server replication.

Creating the CD Library Database on the Server

Before you can use SQL Server CE replication, you must create a publication on the server to subscribe to. You can create a SQL Server publication programmatically or through the Enterprise Manager. Obviously, before you can create a publication, you need to have a database. Figure 8.3 represents a model of the CD library data before the publishing as a replication. Listing 8.4 is the T-SQL that creates this database on the server.

Figure 8.3. An entity relation diagram for CD library database before publication takes place.

graphics/08fig03.gif

Listing 8.4 CreateCDDatabase.sql: Creating the CD Database on the Server
 CREATE TABLE [dbo].[Artist] ([ArtistId] [int] IDENTITY (1, 1) NOT NULL ,     [ArtistName] [varchar] (64) ,     [DefaultGenre] [int] NULL) GO CREATE TABLE [dbo].[CD] ([CDId] [int] IDENTITY (1, 1) NOT NULL ,     [ArtistId] [int] NULL ,     [CDName] [varchar] (64) ,     [YearOfRelease] [int] NULL ,     [Price] [float] NULL ,     [GenreId] [int] NULL ,     [UserId] [int] NULL) GO CREATE TABLE [dbo].[Genre] ([GenreID] [int] IDENTITY (1, 1) NOT NULL ,     [GenreName] [varchar] (64)) GO CREATE TABLE [dbo].[Users] ([UserId] [int] IDENTITY (1, 1) NOT NULL ,     [UserName] [char] (20)) GO ALTER TABLE [dbo].[Artist] WITH NOCHECK ADD     CONSTRAINT [PK_Artist] PRIMARY KEY  CLUSTERED     ([ArtistId])  ON [PRIMARY] GO ALTER TABLE [dbo].[CD] WITH NOCHECK ADD     CONSTRAINT [PK_CD] PRIMARY KEY  CLUSTERED     ([CDId])  ON [PRIMARY] GO ALTER TABLE [dbo].[Genre] WITH NOCHECK ADD     CONSTRAINT [PK_Genre] PRIMARY KEY  CLUSTERED     ([GenreID])  ON [PRIMARY] GO ALTER TABLE [dbo].[Users] WITH NOCHECK ADD     CONSTRAINT [PK_Users] PRIMARY KEY  CLUSTERED     ([UserId])  ON [PRIMARY] GO ALTER TABLE [dbo].[Artist] ADD     CONSTRAINT [FK_Artist_Genre] FOREIGN KEY     ([DefaultGenre]) REFERENCES [dbo].[Genre] ([GenreID]) GO ALTER TABLE [dbo].[CD] ADD     CONSTRAINT [FK_CD_Artist] FOREIGN KEY     ([ArtistId]) REFERENCES [dbo].[Artist] ([ArtistId]),     CONSTRAINT [FK_CD_Genre] FOREIGN KEY     ([GenreId]) REFERENCES [dbo].[Genre] ([GenreID]),     CONSTRAINT [FK_CD_Users] FOREIGN KEY     ([UserId]) REFERENCES [dbo].[Users] ([UserId]) GO 

After you complete your data definitions, create some data in the tables. Listing 8.5 shows the T-SQL to insert some sample data. Using ISQL/W from the SQL Server Tools, open MakeData.sql in ISQL/W and execute it.

Listing 8.5 MakeData.sql: Creating Sample Data in the CD Database
 SET IDENTITY_INSERT Users ON INSERT INTO Users (UserId, UserName) VALUES (1, 'mobileuser1') INSERT INTO Users (UserId, UserName) VALUES (2, 'mobileuser2') INSERT INTO Users (UserId, UserName) VALUES (3, 'mobileuser3') SET IDENTITY_INSERT Users OFF GO SET IDENTITY_INSERT Genre ON INSERT INTO Genre (GenreID, GenreName) VALUES (1, 'Pop') INSERT INTO Genre (GenreID, GenreName) VALUES (2, 'Rock') INSERT INTO Genre (GenreID, GenreName) VALUES (3, 'Bluegrass') SET IDENTITY_INSERT Genre OFF GO SET IDENTITY_INSERT Artist ON INSERT INTO Artist (ArtistId, ArtistName, DefaultGenre)     VALUES (1, 'Britney Spears', 1) INSERT INTO Artist (ArtistId, ArtistName, DefaultGenre)     VALUES (2, 'Dave Matthews Band', 2) INSERT INTO Artist (ArtistId, ArtistName, DefaultGenre)     VALUES (3, 'Allison Krauss', 3) SET IDENTITY_INSERT Artist OFF GO SET IDENTITY_INSERT CD ON INSERT INTO CD (CDId, ArtistId, CDName, YearOfRelease,        Price, GenreId, UserId) VALUES        (1, 1, 'Oops...', 2000, 15.99, 1, 1) INSERT INTO CD (CDId, ArtistId, CDName, YearOfRelease,        Price, GenreId, UserId) VALUES        (2, 1, 'Best Of Britney', 2001, 18.99, 1, 1) INSERT INTO CD (CDId, ArtistId, CDName, YearOfRelease,        Price, GenreId, UserId) VALUES        (3, 2, 'Under the Table and Dreaming', 1998, 10.99, 2, 3) INSERT INTO CD (CDId, ArtistId, CDName, YearOfRelease,        Price, GenreId, UserId) VALUES        (4, 2, 'Everyday', 2001, 15.99, 2, 2) INSERT INTO CD (CDId, ArtistId, CDName, YearOfRelease,        Price, GenreId, UserId) VALUES        (5, 3, 'Union Station', 1996, 12.99, 3, 1) INSERT INTO CD (CDId, ArtistId, CDName, YearOfRelease,        Price, GenreId, UserId) VALUES        (6, 3, 'Best Of...', 1999, 13.99, 3, 1) SET IDENTITY_INSERT CD OFF GO 

Creating a Publication for Replication to a Windows CE Device

Now that you have the CD database created, you can create the publication for replication. This publication uses horizontal filtering to filter the User table with the HOST_NAME() function. The CD table records are then horizontally filtered by using JOIN to join the User table to the CD table.

To invoke the Create Publication Wizard, right-click the CD database in the Enterprise Manager and choose New, Publication. When the wizard opens (see Figure 8.4), leave Show Advanced Options in this Wizard unchecked and click Next because you won't be modifying any advanced options in this publication.

Figure 8.4. Creating a new publication is accomplished through the Create Publication Wizard.

graphics/08fig04.gif

Next, select the CD database in the Choose Publication Database step (see Figure 8.5) and click Next.

Figure 8.5. Selecting the database from which the publication originates.

graphics/08fig05.gif

After selecting the correct database, you need to select the correct publication type. Select the only publication compatible with SQL Server CE, Merge Publication, as shown in Figure 8.6.

Figure 8.6. The only publication type compatible with Windows CE is Merge Publication.

graphics/08fig06.gif

Next, specify what types of subscribers will subscribe to the publication. You can select any subscriber type, but you need to select Devices Running SQL Server CE as shown in Figure 8.7.

Figure 8.7. You can choose the other options without adversely effecting the publication for SQL Server CE.

graphics/08fig07.gif

In the Specify Articles step, include all the tables by selecting them. If no tables are displayed, ensure that Show Unpublished Objects is selected. Your settings should resemble Figure 8.8.

Figure 8.8. Specify the articles to be published in the publication. In this sample, all tables are published as articles.

graphics/08fig08.gif

An identity range needs to be defined for each article whose data will be modifiable on the subscriber. This identity range is a "block" of available primary keys for new records. Managing the identity range allows a subscriber to "block out" a range of identity column values each time it synchronizes with the publication.

Identity ranges are managed on an article basis. To manage the identity range, click the ellipsis () right of the article name to open an article's properties dialog box. To create an identity range, display the Identity Range tab (see Figure 8.9). Select Automatically Assign and Maintain a Unique Identity Range for Each Subscription to enable the rest of the tab's controls. The defaults are an identity range size of 100 at both the publisher and the subscriber. This allows the subscriber to attempt 100 inserts for the article. Also, a default threshold of 80 percent is set for the publisher to start a new range when 80 percent of the range is exhausted. You can adjust these values to meet the needs of your solution. When the identity range for an article is exhausted on the subscriber, no more records may be added to the article until the subscriber syncs with the publisher.

Figure 8.9. Managing an identity range for an article is specified under the Identity Range tab of the article's properties.

graphics/08fig09.gif

You need to create an identity range for each article: Artist, Genre, and CD. No identity range is needed for the Users table because the application won't be modifying records in that table.

After going to the next step, the wizard informs you of any problems with the database for the type of publication you are creating. In this example, there are two problems:

  • The absence of an uniqueidentifier column in each article

  • The identity columns not set to Not For Replication

The wizard displays these problems, how it will fix them, and the implications of the fixes (see Figure 8.10).

Figure 8.10. The Create Publication Wizard analyzes all publication articles for problems, resolves each problem, and explains implications of its resolution(s).

graphics/08fig10.gif

The wizard now prompts for the publication's name (see Figure 8.11). For this example, name the CDPublication and click Next.

Figure 8.11. Naming the publication.

graphics/08fig11.gif

To do some filtering, choose Yes in the customize properties step (see Figure 8.12) and click Next. Because subscriptions from SQL Server CE are always anonymous, you need a method to inform the server which user is subscribing or synching. Using the HostName property on the SSCE.Replication object provides the server with the ability to query that value using the HOST_NAME() function. The publication is filtered to include only the Users table record (as created in MakeData.sql from Listing 8.5) with the UserName field matching the return value from the HOST_NAME() function. Also, the CD records are filtered by JOINing the filtered Users table to the CD table.

Figure 8.12. The wizard has collected enough information to create the publication, but you can define further information before the publication is created.

graphics/08fig12.gif

To filter the articles, the publication needs to have a horizontal filtering set, as shown in Figure 8.13.

Figure 8.13. To filter article rows for subscribers, use horizontal filtering. Vertical filtering applied to articles eliminates columns from the article.

graphics/08fig13.gif

After specifying either type of filtering, the wizard asks whether the filtering will be dynamic or static. The sample application uses dynamic filtering to minimize network bandwidth, so select Yes, Enable Dynamic Filters (see Figure 8.14).

Figure 8.14. The sample application uses dynamic filtering.

graphics/08fig14.gif

The sample application sets the HostName property to the current user allowing the HOST_NAME() function to return the value. This value is compared to the Users.UserName column to filter the Users table to a single record. This record is then JOINed to the CD table to allow only the CD records for that user to be subscribed to.

Select the Users table in the Table to Filter dropdown and add

 UserName = HOST_NAME() 

to the WHERE clause in the text box, as shown in Figure 8.15. Click Next.

Figure 8.15. Filtering the Users article to the current user by using the HOST_NAME() function.

graphics/08fig15.gif

In the Filter Table Rows step, you can specify more filtering to take place on articles. The filter can be specified by adding a WHERE clause to any table (see Figure 8.16). This is accomplished by clicking the ellipsis next to the article name in the top grid. The second type of row filtering that you can apply to the articles is a JOIN condition specified in the lower grid.

Figure 8.16. The Create Publication Wizard allows for horizontal filtering of any articles using a WHERE clause (top grid) or a JOIN clause (lower grid).

graphics/08fig16.gif

The sample application needs to filter the CD article by the UserId foreign key. This is accomplished by adding a JOIN condition to the CD article in the lower grid. As shown in Figure 8.16, choose the Users table (dbo.Users) in the lower grid's Filtered Table column. Next, in the Table to Filter column, choose the CD table (dbo.CD). The JOIN Filter Clause column then informs you to click the ellipsis to open a dialog box, as shown in Figure 8.17.

Figure 8.17. The Specify Join dialog allows you to create the JOIN clause between a filtered table and any other table in the publication.

graphics/08fig17.gif

Enter the JOIN condition between the Users table and the CD table as

 [Users].[UserId] = [CD].[UserId] 

This causes the publication to publish only CD records that belong to the subscriber. After completing this step, your wizard should look like Figure 8.18. Click Next.

Figure 8.18. The CD table is filtered to only publish records that match the User by specifying a JOIN to the filtered Users table.

graphics/08fig18.gif

The next step asks if you want to validate the subscriber information. Because SQL Server CE doesn't support this validation, choose No (see Figure 8.19).

Figure 8.19. You don't need to specify validation of subscriber information because SQL Server CE doesn't validate it.

graphics/08fig19.gif

The next option provides the opportunity to minimize data sent over the network. This sounds like a great option for Windows CE, so choose Yes as shown in Figure 8.20.

Figure 8.20. SQL Server can minimize network traffic for subscriptions by maintaining more sophisticated snapshots of the data at the server.

graphics/08fig20.gif

In the next step (see Figure 8.21), the wizard presents a single choice of Yes for allowing anonymous subscriptions. Because SQL Server CE supports only anonymous subscription, the No option is disabled. Click Next.

Figure 8.21. SQL Server CE only allows for anonymous subscription.

graphics/08fig21.gif

The next step allows for the scheduling of the snapshots for new subscribers. Leaving the default schedule is fine, but be sure Create the First Snapshot Immediately is checked (see Figure 8.22). Click Next.

Figure 8.22. The default schedule is weekly for the snapshot agent to run, but be sure the initial snapshot is created immediately.

graphics/08fig22.gif

Lastly, the publication is created as the last step of the wizard, as shown in Figure 8.23.

Figure 8.23. The publication is created after clicking the Finish button.

graphics/08fig23.gif

Investigating the SQL Server CE Replication Object

Now that you are more familiar with the Microsoft SQL Server replication model, let's look at the SQL Server CE replication object. To use SQL Server CE replication, your application will use the SSCE.Replication ActiveX DLL server. SSCE.Replication is one of three creatable SQL Server CE objects. SSCE.Replication provides access to a Microsoft SQL Server 2000 publisher. It requires quite a few properties configured to properly establish a subscription from the publisher.

Table 8.1 shows the minimal properties that should be set to initialize a publication on SQL Server CE.

Table 8.1. Minimal SQL Server CE Properties to Subscribe to a SQL Server Publication
Property Description and Example
InternetURL URL of the sscesa10.dll: object .InternetURL = "http://www.myserver.com/ssce/sscesa10.dll"
Publication Name of the publication published on the publisher: object. Publication = "MyPublication"
Publisher Name of the Publisher (SQL Server Name): object. Publisher = "MySQLServer"
PublisherAddress Network address of the SQL Server publisher; the publisher can exist on a different server than the IIS server: object. PublisherAddress = "sql.myserver.com" or object. PublisherAddress = "192.0.0.1"
PublisherDatabase Name of the database where the publication resides; the database where the publication resides isn't necessarily the same as the name of the database where the publication originated: object. PublisherDatabase = "MyPublishedDB"
PublisherLogin User ID for the publisher. Depending on the security mode (PublisherSecurityMode) for the publisher, this can be either a SQL Server login or a Windows NT login: object. PublisherSecurityMode = "MyUserId"
PublisherNetwork Network configuration for the SQL Server serving as the publisher. Valid values are DEFAULT_NETWORK, MULTI_PROTOCOL, and TCPIP_SOCKETS: object. PublisherNetwork = TCPIP_SOCKETS
PublisherPassword Password for the user ID. Works in tandem with the PublisherLogin value: object. PublisherPassword = "MyPassword"
PublisherSecurityMode Security mode for the publisher, SQL, or Windows NT. Valid values are DB_AUTHENTICATION and NT_AUTHENTICATION: object. PublisherSecurityMode = DB_AUTHENTICATION
Subscriber Name of the subscriber. Although the publication may allow anonymous subscribers, a value is required: object. Subscriber = "MySubscriberName"
SubscriberConnectionString Connection string to the SQL Server CE database the subscription will be placed: object. SubscriberConnectionString = "Provider=Microsoft.SQLServer.OLEDB.CE.1.0;Data Source=\MySSCEDb.sdf"

After you properly set all the properties in Table 8.1, three different methods are called in the order listed here to create the subscription database (AddSubscription), initialize a subscription (Initialize), and synchronize the data (Run). After these methods are called, the subscriber should call the Terminate method.

AddSubscription marks or creates a database that subscribes to a Microsoft SQL Server publication. It takes a single parameter of type SSCE.REPL_DBADDOPTION. The two different values are CREATE_DATABASE and EXISTING_DATABASE. Using CREATE_DATABASE actually creates a new SQL Server CE database based on the values in the SubscriberConnectionString value. If CREATE_DATABASE is used and the SQL Server CE database specified in the SubscriberConnectionString already exists, an error occurs. AddSubscription is called only once for each database and subscription. If the database already exists, whether created with the AddSubscription method or otherwise , call AddSubscription with the EXISTING_DATABASE value for the parameter.

Initialize prepares for synchronization. It must always be called before the Run method is called.

Note

The database specified must not be open from any ADOCE objects.


Next, Run initiates the communication with the server and then retrieves the subscription. Each time Run is called, all data structures for the subscription are created or modified. If a structure change has occurred in any article of the publication, all modified data is uploaded first, and then the structure changes are implemented before the data comes down to the device.

Lastly, Terminate is called to complete the merge process.

In Listing 8.6, a simple example is given. This example creates a SQL Server CE database, Initializes a subscription, synchronizes with the Run method, and then completes the process by calling Terminate.

Listing 8.6 Creating a SQL Server CE Database that Subscribes to a Publication
 Dim objSSCEReplication As SSCE.Replication ' create the SSCE.Replication object Set objSSCEReplication = CreateObject("SSCE.Replication.1.0") ' the URL for the SSCE dll running on IIS objSSCEReplication.InternetURL "http://192.0.0.1/ssce/sscesa10.dll" ' the SQL server network library setting objSSCEReplication.PublisherNetwork = TCPIP_SOCKETS ' name of the Publisher for replication objSSCEReplication.Publisher = "tblap" ' name of the database objSSCEReplication.PublisherDatabase = "CD" ' name of the publication objSSCEReplication.Publication = "CDPublication" ' security mode to authenticate to the SQL server objSSCEReplication.PublisherSecurityMode = DB_AUTHENTICATION ' sql server login objSSCEReplication.PublisherLogin = "sa" ' sql server password objSSCEReplication.PublisherPassword = "" ' IP address of the publisher objSSCEReplication.PublisherAddress = "192.0.0.1" ' Set connection properties objSSCEReplication.SubscriberConnectionString = "Data Source=\CD.sdf" ' set the subscriber name objSSCEReplication.Subscriber = "mobileuser1" ' if the first time, we need to create the DB ' while we're adding a subscription objSSCEReplication.AddSubscription CREATE_DATABASE ' initialize the subscription on the device objSSCEReplication.Initialize ' sync the subscription objSSCEReplication.Run ' close out and complete objSSCEReplication.Terminate 

Let's examine Listing 8.3 line by line. First, an object reference is declared as an SSCE.Replication object:

 Dim objSSCEReplication As SSCE.Replication 

The SSCE.Replication object is then created:

 Set objSSCEReplication = CreateObject("SSCE.Replication.1.0") 

Next, the InternetURL property is set to the URL of the SQL Server CE DLL residing on the server:

 objSSCEReplication.InternetURL "http://192.0.0.1/ssce/sscesa10.dll" 

Remember, this is the virtual directory you created, copied the DLLs into, and registered by using RegSrv32.

The PublisherNetwork property is set to the value on which the server-side SQL Server listens:

 objSSCEReplication.PublisherNetwork = TCPIP_SOCKETS 

The Publisher property is set to the named instance of the SQL server that serves as the publication's publisher:

 objSSCEReplication.Publisher = "tblap" 

The PublisherDatabase property is set to the name of the database from which the publication originates:

 objSSCEReplication.PublisherDatabase = "CD" 

The Publication property is set to the name of the publication:

 objSSCEReplication.Publication = "CDPublication" 

The publication name can vary from the database from which it originates. There can even be multiple publications for a individual database.

The PublisherSecurityMode property is set to the type of authentication the subscriber (devices) desires to use as its authentication method:

 objSSCEReplication.PublisherSecurityMode = DB_AUTHENTICATION 

The SQL Server must be configured to use the specified security mode.

The PublisherLogin property is set to the SQL Server login that has access to the publication. In this example, the sa login is used; your security policies may dictate a different policy.

 objSSCEReplication.PublisherLogin = "sa" 

The PublisherPassword property is set to the valid password that corresponds to the value for PublisherLogin:

 objSSCEReplication.PublisherPassword = "" 

The PublisherAddress property is set to the network address of the publisher SQL Server:

 objSSCEReplication.PublisherAddress = "192.0.0.1" 

Remember, the publisher might be in a different location than the IIS server hosting the SQL Server CE DLLs.

The SubscriberConnectionString property is set with the Data Source syntax for the location of the SQL Server database in which the subscription should be created:

 objSSCEReplication.SubscriberConnectionString = "Data Source=\CD.sdf" 

The Subscriber property is set to the subscriber's name:

 objSSCEReplication.Subscriber = "mobileuser1" 

The AddSubscription method is called by using the CREATE_DATABASE enumeration value:

 objSSCEReplication.AddSubscription CREATE_DATABASE 

If the SQL Server CE database exists, as specified as the Data Source in the SubscriberConnectionString property, the EXISTING_DATABASE enumeration value should be used instead.

The Initialize method is called, preparing the object and database to synchronize:

 objSSCEReplication.Initialize 

The Run method is called to actually subscribe to the subscription, and receive the structures and data:

 objSSCEReplication.Run 

The Terminate method is called to perform cleanup and complete the merging process:

 objSSCEReplication.Terminate 

Converting the CD Library Application to Use SQL Server CE Replication

At this point, you should have the CD database created, a publication ready, and a good understanding of the SQL Server CE replication object. Let's get started converting that application. If you didn't create the CD Library application, you can use the sample code from Chapter 7. This chapter presents only added and modified code from the material presented in Chapter 7.

Project-Level Changes

The application needs to reference three new ActiveX libraries. The first is SQL Server CE itself. To reference SQL Server CE, you need to actually reference a type library instead of a DLL. By using the Browse button in the project references dialog, navigate to type library file ca_merge.tlb. The location of this file using a default install is C:\Program Files\Microsoft SQL Server CE\Lib\ca_merge.tlb. You may be prompted that the selected library isn't for your current platform; add it anyway. SQL Server CE appears in the dialog box as Microsoft SQL Server CE Control 1.0, even if it's version 1.1, the most current version at the time of this writing.

The second and third libraries that need a reference set should be available in your project references list already. The second library is Microsoft CE ADO Control 3.1, ADOCE version 3.1. SQL Server CE requires version 3.1 (versus Pocket Access compatibility to earlier versions) and should have been installed when you installed the developer tools for SQL Server CE from the install package. The third library needed is Microsoft CE ADO Ext 3.1 for DDL. This library is the CE version of ADOX, used for creating databases, tables, indexes, and other data structures in a SQL Server CE database. We will use this ADOXCE library to create a table for the storage of configuration parameters. Your project references dialog should now look similar to Figure 8.24.

Figure 8.24. The CD Library Replication Application should have project references to Microsoft CE ADO Control 3.1, SQL Server CE Control 1.0 and Microsoft CE ADO Ext. for DDL 3.1.

graphics/08fig24.gif

Other project-level changes that you need to accomplish before digging in are to add a new module file named modSQLRepl.bas and a form named frmSQLProperties. After adding the module file, stub out a Sub Main in it, so you can change the Startup Object to Sub Main (see Figure 8.25). Also, add a FileSystem control named frmSQLProperties. Notice in Figure 8.25 that the project's Update Components settings are set to force down the SQL Server CE DLLs to a device as discussed for Figure 8.2.

Figure 8.25. The CD Library Replication project needs to use Sub Main as its Startup Object.

graphics/08fig25.gif

Digging In: New Consts and Public Variables

Some new Consts and Public variables need to be declared to make the CD Library application compliant to SQL Server CE. These are places in the general section of module file modSQlRepl.bas, as shown in Listing 8.7.

Listing 8.7 modSQLRepl.bas: Consts and Public Variables
 ' the database path and name Public Const DB_PATH = "\CD.sdf" ' the config parms table Public Const CONFIG_PARAMS_TABLE = "ConfigParms" Public gblnFirstStartUp As Boolean Public gstrInternetURL As String Public gstrPublisherAddress As String Public gstrPublisherNetwork As SSCE.REPL_NETWORK_TYPE Public gstrPublisher  As String Public gPublisherSecurityMode As SSCE.REPL_SECURITY_TYPE Public gstrPublisherLogin As String Public gstrPublisherPassword As String Public gstrSubscriberConnectionString  As String Public gstrSubscriber  As String Public gstrHostName  As String Public gstrPublication As String Public gstrPublisherDatabase As String Public gstrIPAddress As String Public glngCurrentUser As Long ' the SQL Server CE replication object Public gSQLReplication As SSCE.Replication ' ADOXCE Catalog - Used to check for the ' config table's existence Public gobjADOXCatalog As ADOXCE.Catalog ' configuration recordset Public gConfigRecord As ADOCE.Recordset ' used to retrieve user id Public gobjUser As ADOCE.Recordset ' class names Public Const SSCE_REPLICATION_CLASS = "SSCE.Replication.1.0" Public Const ADOXCE_CATALOG_CLASS = "ADOXCE.Catalog.3.1" ' AddSubscription Consts Public Const EXISTING_DATABASE = 0 Public Const CREATE_DATABASE = 1 ' NetworkType Consts Public Const DEFAULT_NETWORK = 0 Public Const TCPIP_SOCKETS = 1 Public Const MULTI_PROTOCOL = 2 ' Authentication Consts Public Const DB_AUTHENTICATION = 0 Public Const NT_AUTHENTICATION = 1 

Examining Listing 8.7 by sections, the first two Consts are the database path name and a table name for the configuration parameters table, ConfigParams. The configuration parameters table stores the property settings (SSCE.Replication object) used for a successful subscription initialization or synchronization. This allows ease of use for the user without having to manually configure the settings every time a synchronization is desired.

The second section (starting Public gstrInternetURL As String) is a collection of Public variables that store the SSCE.Replication configuration settings from the initial hard-coded values, the configuration form (frmSQLProperties), or as retrieved from the ConfigParams table.

The third section declares the references for the SSCE.Replication object, an ADOXCE.Catalog to check for the existence of the ConfigParams table, an ADOCE.Recordset to retrieve and update the configuration values in the ConfigParams table, and another recordset to retrieve the primary key value for the current user.

The fourth section, labeled class names, declares two Consts for the SSCE.Replication and ADOXCE.Catalog class names and versions. These are present for maintainability; for more information about this strategy, please see Chapter 7.

The final three sections declare Consts used as values for various SSCE.Replication properties.

Note

The Consts declared in the final three sections may not be necessary in your development environment. I've experienced some environments that require them and some that don't. I've included them here for you; if you receive errors when trying to access them, simply comment them out or remove them.


modSQLRepl.bas: Sub Main, Where the Journey Begins

Because application initialization takes quite a bit of configuration before the subscription arrives, a Sub Main (see Listing 8.8) is implemented to decide where to go.

Listing 8.8 modSQLRepl.bas: Initializing the Application
 Sub Main()     On Error Resume Next     ' initialize all global variables     If Not InitializeGlobals() Then         MsgBox "Error initializing global objects."         Exit Sub     End If     ' default to first startup     gblnFirstStartUp = True     ' if the database exists then set first start up to false If Len(frmSQLProperties.FileSystem1.Dir(DB_PATH)) > 0 Then         gblnFirstStartUp = False     End If     ' if we can't load the configs, then inform the     ' user and exit     If Not LoadConfigs() Then         MsgBox "Error loading configuration."     End If     ' if first startup     If gblnFirstStartUp Then         ' let's show the sql form         ' allow user to check/set the values         frmSQLProperties.Show     Else         ' we're all set, show the main form         frmMain.Show     End If End Sub 

The code in Listing 8.8 starts by creating all the objects the application uses by calling InitializeGlobals. Code has been added to InitializeGlobals to create the objects added to CD Library Replication, as shown shortly in Listing 8.9.

Next, a global Boolean flag, gblnFirstStartUp, is set to True, indicating this is the first time the application has been run on the device. This flag determines the first form to show and whether the subscription needs to be created.

Next, the FileSystem object checks for the existence of the SQL Server CE database file used by the application. If the database file exists, the flag gblnFirstStartUp is set to False, indicating the application can proceed with a normal startup.

LoadConfigs (coming up soon in Listing 8.10) is then called to populate all the Public variables that hold the configuration values for synchronizing the subscription to the publication on the SQL server. These values are used by the SSCE.Replication object, gSQLReplication. When LoadConfigs is called without a current subscription (the first time), it will populate the values from hard-coded values; otherwise, it will load them from the ConfigParams table.

After the configuration parameters are loaded, Sub Main will display one of two forms. If the application is currently in a "first-run" state, as designated by the variable gblnFirstStartUp, the form frmSQLProperties is shown. If the application is a normal state, the form frmMain is shown.

modGlobal.bas: InitializeGlobals, Where All Objects Are Created

InitializeGlobals in Listing 8.9 creates all the ActiveX objects used in the CD Library Replication application. This function is the same with four new objects being created. For the original discussion of InitializeGlobals, see Listing 7.1 in Chapter 7.

Listing 8.9 modGlobal.bas: Changes to InitializeGlobals
 Function InitializeGlobals() As Boolean     On Error Resume Next     InitializeGlobals = False     ' initialize globals - this needs to take place     ' only once, due to CreateObject memory leak     ' ADOCE_CLASS_CONNECTION & ADOCE_CLASS_RECORDSET     ' are used because ce references these classes     ' by their version numbers, create Consts allows     ' changing of these version quickly and accurately     Set gobjConnection = CreateObject(ADOCE_CLASS_CONNECTION)     Set gobjArtist = CreateObject(ADOCE_CLASS_RECORDSET)     Set gobjCD = CreateObject(ADOCE_CLASS_RECORDSET)     Set gobjGenre = CreateObject(ADOCE_CLASS_RECORDSET)     Set gobjPKEY = CreateObject(ADOCE_CLASS_RECORDSET)  Set gConfigRecord = CreateObject(ADOCE_CLASS_RECORDSET)   Set gSQLReplication = CreateObject(SSCE_REPLICATION_CLASS)   Set gobjADOXCatalog = CreateObject(ADOXCE_CATALOG_CLASS)   Set gobjUser = CreateObject(ADOCE_CLASS_RECORDSET)  If Err.Number = 0 Then         InitializeGlobals = True     End If End Function 

InitializeGlobals creates four new objects to support CD Library Replication.

modSQLRepl.bas: LoadConfigs, Finding the Server

LoadConfigs (see Listing 8.10) populates the Public variables used to configure the SSCE.Replication object gSQLReplication. On the application's first run, the values are populated from hard-coded values. Subsequent execution of CD Library Replication will retrieve these values from the ConfigParams table. These values are saved to the ConfigParams table using the function SaveConfiguration after a successful initialization of the subscription.

Listing 8.10 modSQLRepl.bas: Populating the Configuration Parameters for Creating or Synchronizing the Subscription
 Public Function LoadConfigs() As Boolean     On Error Resume Next     ' set to error condition for early exit     LoadConfigs = False     ' are we here the first time     ' if so, let's set the values     If gblnFirstStartUp Then         ' ip address of the web server         gstrIPAddress = "192.0.0.1"         ' URL for the SSCE dll         gstrInternetURL = "http://" _             & gstrIPAddress & "/ssce/sscesa10.dll"         ' name of the Publisher (SQL)         gstrPublisher = "tblap"         ' security mode that the publisher uses         gPublisherSecurityMode = DB_AUTHENTICATION         ' login id for the publisher         gstrPublisherLogin = "sa"         ' password         gstrPublisherPassword = ""         ' network library for the publisher         gstrPublisherNetwork = TCPIP_SOCKETS         ' name of the database for the publication (on the publisher)         gstrPublisherDatabase = "CD"         ' name of the subscriber gstrSubscriber = "mobileuser1"         ' host name - used to filter the data         gstrHostName = "mobileuser1"         ' name of the publication         gstrPublication = "CDPublication"     Else         ' open the database to get the SQL CE Replication configurations         If Not OpenDatabase() Then             ' error - get out             Exit Function         End If         ' open the config table         gConfigRecord.Open CONFIG_PARAMS_TABLE, gobjConnection, _             adOpenForwardOnly, adLockReadOnly, adCmdTableDirect         ' get the values         gstrInternetURL = gConfigRecord.Fields("InternetURL").Value         gstrPublisherNetwork = gConfigRecord.Fields("PublisherNetwork").Value         gstrPublisher = gConfigRecord.Fields("Publisher").Value         gPublisherSecurityMode = _             gConfigRecord.Fields("PublisherSecurityMode").Value gstrPublisherLogin = gConfigRecord.Fields("PublisherLogin").Value         gstrPublisherPassword = gConfigRecord.Fields("PublisherPassword").Value         gstrSubscriber = gConfigRecord.Fields("Subscriber").Value         gstrHostName = gConfigRecord.Fields("HostName").Value         gstrPublication = gConfigRecord.Fields("Publication").Value         gstrIPAddress = gConfigRecord.Fields("IPAddress").Value         gstrPublisherDatabase = gConfigRecord.Fields("PublisherDB").Value         ' close it         gConfigRecord.Close     End If     ' set the return value to True if there     ' was no error     LoadConfigs = Err.Number = 0 End Function 

Outside the error handling and setting of the return value for the function, LoadConfigs is basically two code blocks. The code block to run is determined by an If statement as to status of the flag gblnFirstStartUp. If it's the first run of the application (gblnFirstStartUp = True), the configuration variables are set by the execution of the hard-coded code block. When entering the hard-coded code block, you can change the values to match your configuration, or enter them as they appear here. We'll be creating a form that displays all of them for configuration. After modifying their contents and receiving a valid subscription, they will be saved, so you shouldn't have to make the changes repeatedly.

The second code block (after the Else) opens a recordset from the ConfigParams table and populates the values from this recordset.

frmSQLProperties: Setting Configuration Properties at Runtime

With the first startup, Sub Main shows the frmSQLProperties form, which allows configuration of the values for the SSCE.Replication object gSQLReplication. This form works by using a multiframe wizardlike interface. There are three frames to this wizard, as shown in Figures 8.26, 8.27 and 8.28. The form uses Next and Back buttons to navigate through the three frames.

Figure 8.26. The first frame of frmSQLProperties allows for configuration of the IIS SQL Server CE URL, the publisher address, the name of the publisher, and the publisher login and password.

graphics/08fig26.gif

Figure 8.27. The second frame allows for configuration of the security mode for authentication on the publisher, the network protocol to communicate with the publisher, the name of the database on the publisher, the subscriber name, and the host name.

graphics/08fig27.gif

Figure 8.28. The third and last frame allows for configuration of the publication name and a button to initialize or synchronize the subscription.

graphics/08fig28.gif

The form frmSQLProperties is only one form, with three different frames placed onto it. This methodology provides speedy performance and was quite necessary in the days of the 70MHz MIPS processor. The performance need for this trick may no longer be needed with the 206MHz StrongArm processors out there, but if you ever create an application with a tab set, you'll want to pull it out of your Windows CE programming toolbox. Look at the form at design time in Figure 8.29; the three frames are randomly placed on the form and are shaded so that you can see their borders clearly.

Figure 8.29. Form frmSQLProperties at design time shows all three frames clearly by shading their backgrounds.

graphics/08fig29.gif

It should be easiest for you to retrieve the visual portions of the form from the sample code files, but let's look at the code behind this form that makes it work.

One Public variable, CurrentFrame, is declared to keep track of which frame should be currently displayed:

 ' currently displayed frame Public CurrentFrame As Integer 
Form.Load: Moving the Frames to Their Correct Positions

In the Form.Load event of frmSQLProperties (see Listing 8.11), all the frames are moved to the form's upper-left corner.

Listing 8.11 frmSQLProperties.frm: Moving the Frames
 Private Sub Form_Load()     ' move all frames to proper position     Me.Frame1.Top = 0     Me.Frame1.Left = 0     Me.Frame2.Top = 0     Me.Frame2.Left = 0     Me.Frame3.Top = 0     Me.Frame3.Left = 0 End Sub 
Form.Activate: Initializing the Controls

In the Form.Activate event of frmSQLProperties (see Listing 8.12), all the controls are initialized to the current values of the Public replication configuration values.

Listing 8.12 frmSQLProperties.frm: Initializing the Controls' Values
 Private Sub Form_Activate()     ' set the current frame to 1     CurrentFrame = 1     ' Refresh the frames     RefreshFrames     ' set the values from the globals     Me.txtURL.Text = gstrInternetURL     Me.txtPublisherAddress.Text = gstrIPAddress     Me.txtPublisherName.Text = gstrPublisher     Me.txtPublisherLogin.Text = gstrPublisherLogin     Me.txtPublisherPassword.Text = gstrPublisherPassword     Me.txtDB.Text = gstrPublisherDatabase     Me.txtSubscriberName.Text = gstrSubscriber     Me.txtHostName.Text = gstrHostName     Me.txtPublication.Text = gstrPublication     ' set security mode radio buttons     If gPublisherSecurityMode = DB_AUTHENTICATION Then         Me.optSecDB = True     Else         Me.optSecWin = True     End If     ' set network protocol radio buttons     Select Case gstrPublisherNetwork         Case TCPIP_SOCKETS             Me.optNetTCPIP = True         Case MULTI_PROTOCOL             Me.optNetMulti = True         Case DEFAULT_NETWORK             Me.optNetworkDef = True     End Select End Sub 

The first item accomplished in Form.Activate is to set the Public variable CurrentFrame to 1 to ensure that the first frame is shown when the form displays. Then RefreshFrames (see Listing 8.13) is called to make the proper frame and buttons visible.

Next, all controls on all three frames are set to the current values from their corresponding Public variables. First, all the textboxes are set, and then the two sets of option buttons are set.

RefreshFrames: Displaying the Correct Frame and the Next/Back Buttons

RefreshFrames (see Listing 8.13) uses three simple formulas to set the Visible property for the current frame to be displayed based on the value of CurrentFrame. It then sets the Enabled property of both the Next and Back buttons also from the value of CurrentFrame.

Listing 8.13 frmSQLProperties.frm : Displaying the Correct Frame and Buttons
 Private Sub RefreshFrames()     ' set each frames visible to true or     ' false as to whether it is the current frame     Me.Frame1.Visible = (CurrentFrame = 1)     Me.Frame2.Visible = (CurrentFrame = 2)     Me.Frame3.Visible = (CurrentFrame = 3)     ' next should only be visible if frame 2 or 3     Me.cmdBack.Enabled = (CurrentFrame > 1)     ' back should only be visible if frame 1 or 2     Me.cmdNext.Enabled = (CurrentFrame < 3)End Sub 
cmdBack.Click and cmdNext.Click: Navigating Through the Frames

Both the cmdNext.Click and cmdBack.Click events (see Listing 8.14) change the value of CurrentFrame and refresh the form with RefreshFrames. Sub cmdNext.Click increments CurrentFrame by 1, whereas Sub cmdBack.Click decrements it by 1.

Listing 8.14 frmSQLProperties.frm: Navigating Through the Frames Using cmdBack.Click and cmdNext.Click
 Private Sub cmdNext_Click()     If CurrentFrame < 3 Then         ' move forward one frame         CurrentFrame = CurrentFrame + 1         ' refresh the frames         RefreshFrames     End IfEnd Sub Private Sub cmdBack_Click()     If CurrentFrame > 1 Then         ' move back one frame         CurrentFrame = CurrentFrame - 1         ' refresh the frames         RefreshFrames     End IfEnd Sub 
cmdInitialize.Click: Initializing the Subscription

In Listing 8.15, cmdInitialize.Click retrieves all the values from all controls located within the three frames and sets these values to the appropriate configuration Public variables. It then calls the Sync function to create the subscription. With a successful call to Sync, the current state of the configuration is saved to the ConfigParams table using the SaveConfiguration function.

Listing 8.15 frmSQLProperties.frm: Setting the Configuration Values, Initializing the Subscription, and Saving the Configuration
 Private Sub cmdInitialize_Click()     ' set the global properties from the     ' form's values     gstrInternetURL = Me.txtURL.Text     gstrPublisherAddress = Me.txtPublisherAddress.Text     gstrPublisher = Me.txtPublisherName.Text     gstrPublisherLogin = Me.txtPublisherLogin.Text     gstrPublisherPassword = Me.txtPublisherPassword.Text     gstrSubscriber = Me.txtSubscriberName.Text     gstrHostName = Me.txtHostName.Text     gstrPublication = Me.txtPublication.Text     gstrPublisherDatabase = Me.txtDB.Text     gstrIPAddress = Me.txtPublisherAddress.Text     ' radio button set     Select Case True         Case Me.optNetMulti.Value             gstrPublisherNetwork = MULTI_PROTOCOL         Case Me.optNetTCPIP.Value             gstrPublisherNetwork = TCPIP_SOCKETS         Case Me.optNetworkDef.Value             gstrPublisherNetwork = DEFAULT_NETWORK     End Select     ' radio button set     Select Case True         Case Me.optSecDB.Value             gPublisherSecurityMode = DB_AUTHENTICATION         Case Me.optSecWin.Value             gPublisherSecurityMode = NT_AUTHENTICATION     End Select     ' Sync     If Not Sync() Then         MsgBox "Errors occurred in creating subscription." Exit Sub     End If     If Not SaveConfiguration() Then         MsgBox "Could not save configuration."         Exit Sub     End If     ' show the main form     frmMain.Show End Sub 

The majority of cmdInitialize.Click (up to the Sync call) sets the configuration values, which is basically a reversal of the Form.Activate event (refer to Listing 8.12), which sets the control values from the configuration values.

Next, the Sync Function (shown soon in Listing 8.16) is called. If Sync isn't successful, a message box informs users of the failure and the routine is exited.

Continuing on (Sync was successful), the configuration is saved using the SaveConfiguration function.

modSQLRepl.bas: Sync, Getting at That Data

The Sync function uses the SSCE.Replication object, gSQLReplication, to create the database and initialize the subscription (see Listing 8.16).

Listing 8.16 modReplSQL.bas: Initializing the Subscription
 Function Sync() As Boolean     On Error Resume Next     'set the error state so we can leave quickly     Sync = False     ' we have to close the database     ' before we Sync to the remote server     If Not CloseDatabase() Then         ' problem closing - let's exit with         ' an error condition         Exit Function     End If     ' the URL for the SSCE dll running on IIS     gSQLReplication.InternetURL = gstrInternetURL     ' the SQL server network library setting     gSQLReplication.PublisherNetwork = TCPIP_SOCKETS     ' name of the Publisher for replication     gSQLReplication.Publisher = gstrPublisher     ' name of the database     gSQLReplication.PublisherDatabase = gstrPublisherDatabase     ' name of the publication     gSQLReplication.Publication = gstrPublication     ' security mode to authenticate to the SQL server     gSQLReplication.PublisherSecurityMode = gPublisherSecurityMode     ' login     gSQLReplication.PublisherLogin = gstrPublisherLogin     ' password     gSQLReplication.PublisherPassword = gstrPublisherPassword     ' IP address of the publisher     gSQLReplication.PublisherAddress = gstrIPAddress     ' Set Subscriber properties     gSQLReplication.SubscriberConnectionString = "Data Source=" & DB_PATH     ' set the subscriber name     gSQLReplication.Subscriber = gstrSubscriber     ' set the host name     gSQLReplication.HostName = gstrSubscriber     ' if the first time, we need to create the DB     ' while we're adding a subscription     If gblnFirstStartUp Then         gSQLReplication.AddSubscription CREATE_DATABASE     End If     ' initialize the subscription on the device     gSQLReplication.Initialize     ' check for errors     If gSQLReplication.ErrorRecords.Count > 0 Then         ' display any errors         ConnErrRoutine gSQLReplication     Else         ' Sync with server         gSQLReplication.Run         ' check for errors         If gSQLReplication.ErrorRecords.Count > 0 Then             ' display errors             ConnErrRoutine gSQLReplication         End If     End If     ' close out and complete     gSQLReplication.Terminate     ' open or reopen the database If Not OpenDatabase() Then         Exit Function     End If     ' set the return value to true if no error     Sync = Err.Number = 0     ' check the status     If Sync Then         MsgBox "Sync completed successfully.", , "CD Library"     Else         MsgBox "Sync completed with errors.", , "CD Library"     End If End Function 

Let's break down the Sync function and investigate it. Setup is completed in Listing 8.17.

Listing 8.17 Setting the Error Condition and Closing the Database
 'set the error state so we can leave quickly Sync = False ' we have to close the database ' before we Sync to the remote server If Not CloseDatabase() Then     ' problem closing - let's exit with     ' an error condition     Exit Function End If 

First, in Listing 8.17, the return value is set to an error condition so that you can make a quick exit. Then the database is closed using the CloseDatabase function. The database needs to be closed to successfully complete a synchronization process with the publication on the server. Although you don't have a database at this point (if this is the first synchronization), CloseDatabase is constructed in such a way that it won't error, but should still return a success value of True.

Listing 8.18 sets all the configuration variables on the SSCE.Replication object gSQLReplication. It also uses the Const DB_PATH to set the Data Source attribute of the SubscriberConnectionString property.

Listing 8.18 Transferring the Configuration Values to the Replication Object
 ' the URL for the SSCE dll running on IIS gSQLReplication.InternetURL = gstrInternetURL ' the SQL server network library setting gSQLReplication.PublisherNetwork = gstrPublisherNetwork ' name of the Publisher for replication gSQLReplication.Publisher = gstrPublisher ' name of the database gSQLReplication.PublisherDatabase = gstrPublisherDatabase ' name of the publication gSQLReplication.Publication = gstrPublication ' security mode to authenticate to the SQL server gSQLReplication.PublisherSecurityMode = gPublisherSecurityMode ' login gSQLReplication.PublisherLogin = gstrPublisherLogin ' password gSQLReplication.PublisherPassword = gstrPublisherPassword ' IP address of the publisher gSQLReplication.PublisherAddress = gstrIPAddress ' Set Subscriber properties gSQLReplication.SubscriberConnectionString = "Data Source=" & DB_PATH ' set the subscriber name gSQLReplication.Subscriber = gstrSubscriber ' set the host name gSQLReplication.HostName = gstrSubscriber 

Listings 8.18 and 8.19 correlate to the mandatory values to create a subscription to a publication or synchronize the subscription with the publication.

Listing 8.19 Initializing the Subscription
 ' if the first time, we need to create the DB ' while we're adding a subscription If gblnFirstStartUp Then     gSQLReplication.AddSubscription CREATE_DATABASE End If 

If it's determined that this is a first time initialization with the publication, you need to call AddSubscription to properly initialize the SSCE.Replication object. Also, the CREATE_DATABASE parameter is used for the AddSubscription method because you currently shouldn't have a SQL Server CE database file (if this is the first time you have attempted to subscribe to the publication).

Listing 8.20 finally retrieves the subscription from the publication.

Listing 8.20 Retrieving the Subscription
 ' initialize the subscription on the device gSQLReplication.Initialize ' check for errors If gSQLReplication.ErrorRecords.Count > 0 Then     ' display any errors     ConnErrRoutine gSQLReplication Else     ' Sync with server     gSQLReplication.Run     ' check for errors     If gSQLReplication.ErrorRecords.Count > 0 Then         ' display errors         ConnErrRoutine gSQLReplication     End If End If 

Before a subscription can actually be initialized or synchronized using the Run method, the Initialize method needs to be called as shown in Listing 8.20. Initialize prepares the SSCE.Replication for synchronizing with the publication. At this point, the SQL Server CE database is created.

If there are no ErrorRecords after calling the Initialize method, the Run method is called to actually retrieve the subscription or synchronize it. If there are ErrorRecords, ConnErrRoutine is called passing the SSCE.Replication object gSQLReplication.

Listing 8.21 starts the cleanup for the Sync function.

Listing 8.21 Finishing the Merge Replication and Cleaning Up
 ' close out and complete gSQLReplication.Terminate ' open or repopen the database If Not OpenDatabase() Then     Exit Function End If 

The call to the Terminate method actually finishes the merging of data in the SQL Server CE database (if the database existed and had previously subscribed to the publication).

Also in Listing 8.21, the database is opened, or reopened in the case that it was previously present and open using the OpenDatabase Function.

In Listing 8.22, the return value is set to True if there were no errors. Also, the status of the sync process is displayed to users based on the return value.

Listing 8.22 Setting the Return Value and Informing Users of the Synchronization's Status
 ' set the return value to true if no error Sync = (Err.Number = 0) ' check the status If Sync Then     MsgBox "Sync completed successfully.", , "CD Library" Else     MsgBox "Sync completed with errors.", , "CD Library" End If 
modSQLRepl.bas: SaveConfiguration, Saving the Configuration Information to Synchronize with the Publisher

SaveConfiguration in Listing 8.23 allows any changes made at runtime to the hard-coded values in LoadConfigs (see Listing 8.10) to be saved. This allows quick and easy synchronization to occur without significant effort on the user's behalf .

Listing 8.23 modReplSQL.bas: Storing the Replication Configuration for Future Syncs
 Function SaveConfiguration() As Boolean     Dim objADOXTable As ADOXCE.Table     Dim blnTableFound As Boolean     On Error Resume Next     ' set error condition so we can exit early     SaveConfiguration = False     ' set the ADOX catalog connection to the db connection     Set gobjADOXCatalog.ActiveConnection = gobjConnection     ' set flag that we have not found ' the configuration table     blnTableFound = False     ' cycle through each table in the DB     For Each objADOXTable In gobjADOXCatalog.Tables         ' check if it is the ConfigParams Table         If objADOXTable.Name = CONFIG_PARAMS_TABLE Then             ' if we found it, set the flag and get out             blnTableFound = True             Exit For         End If     Next     ' if we did not find the table, let's create it     If Not blnTableFound Then         ' create the table         gobjConnection.Execute "CREATE TABLE " _             & CONFIG_PARAMS_TABLE _             & " (InternetURL nvarchar(255), PublisherNetwork tinyint, " _             & "Publisher nvarchar(255), PublisherDB nvarchar(255), " _             & "PublisherSecurityMode tinyint, PublisherLogin nvarchar(255), " _             & "PublisherPassword nvarchar(255), Subscriber nvarchar(255), " _             & "HostName nvarchar(255), Publication nvarchar(255), " _             & "IPAddress nvarchar(255))"     End If     ' open it for adding/updating     gConfigRecord.Open CONFIG_PARAMS_TABLE, gobjConnection, _         adOpenForwardOnly, adLockOptimistic, adCmdTableDirect     ' there should only be one record, so if there     ' are not, add a record     If gConfigRecord.EOF Then         ' add the record         gConfigRecord.AddNew     End If     ' save all the configuration data     gConfigRecord.Fields("InternetURL") = gstrInternetURL     gConfigRecord.Fields("PublisherNetwork") = gstrPublisherNetwork     gConfigRecord.Fields("Publisher") = gstrPublisher     gConfigRecord.Fields("PublisherSecurityMode") = gPublisherSecurityMode     gConfigRecord.Fields("PublisherLogin") = gstrPublisherLogin     gConfigRecord.Fields("PublisherPassword") = gstrPublisherPassword     gConfigRecord.Fields("Subscriber") = gstrSubscriber     gConfigRecord.Fields("HostName") = gstrHostName     gConfigRecord.Fields("Publication") = gstrPublication     gConfigRecord.Fields("IPAddress") = gstrIPAddress     gConfigRecord.Fields("PublisherDB") = gstrPublisherDatabase     ' send the changes     gConfigRecord.Update     ' if we're done with no errors let's     ' clear the first time flag and set     ' return value to success     If Err.Number = 0 Then         gblnFirstStartUp = False         SaveConfiguration = True     End If End Function 

Let's break down SaveConfiguration. Listing 8.24 declares a local ADOXCE.Table object and Boolean. The ADOXCE.Table object objADOXTable is used with the global ADOXCE.Catalog object gobjADOXCatalog to determine whether the configuration parameters table ConfigParams is present. Next, the gobjADOXCatalog is given a reference to the database using its ActiveConnection property. Lastly, the Boolean variable blnTableFound is explicitly set to False, making the assumption that ConfigParams doesn't exist.

Listing 8.24 Getting Set Up and Setting the Catalog's Connection
 Dim objADOXTable As ADOXCE.Table Dim blnTableFound As Boolean On Error Resume Next ' set error condition so we can exit early SaveConfiguration = False ' set the ADOX catalog connection to the db connection Set gobjADOXCatalog.ActiveConnection = gobjConnection     ' set flag that we have not found ' the configuration table blnTableFound = False 

Listing 8.25 first presents a For...Each loop that enumerates through each table contained within the ADOXCE.Catalog's Tables collection. Each enumerated ADOXCE.Table object is checked to see if it's the ConfigParams table. If it is, the Boolean is set to True and the For...Each loop is exited.

Next in Listing 8.25, if the table hasn't been found, a SQL DDL statement is issued to create it.

Listing 8.25 Searching for the ConfigParams Table and Creating It If Not Found
 ' cycle through each table in the DB For Each objADOXTable In gobjADOXCatalog.Tables     ' check if it is the ConfigParams Table     If objADOXTable.Name = CONFIG_PARAMS_TABLE Then         ' if we found it, set the flag and get out         blnTableFound = True         Exit For     End If Next ' if we did not find the table, let's create it If Not blnTableFound Then     ' create the table     gobjConnection.Execute "CREATE TABLE " _         & CONFIG_PARAMS_TABLE _         & " (InternetURL nvarchar(255), PublisherNetwork tinyint, " _         & "Publisher nvarchar(255), PublisherDB nvarchar(255), " _         & "PublisherSecurityMode tinyint, PublisherLogin nvarchar(255), " _         & "PublisherPassword nvarchar(255), Subscriber nvarchar(255), " _         & "HostName nvarchar(255), Publication nvarchar(255), " _         & "IPAddress nvarchar(255))" End If 

In Listing 8.26, whether the table previously existed or was just created, the ADOCE.Recordset object Opens the table. If there's no record within the recordset, by testing the EOF property, a record is appended using the AddNew method of the ADOCE.Recordset object.

Listing 8.26 Opening the ConfigParams Table and Ensuring a Record for Saving Values
 ' open it for adding/updating     gConfigRecord.Open CONFIG_PARAMS_TABLE, gobjConnection, _         adOpenForwardOnly, adLockOptimistic, adCmdTableDirect ' there should only be one record, so if there ' are not, add a record If gConfigRecord.EOF Then     ' add the record     gConfigRecord.AddNew End If 

Listing 8.27 simply transfers the values from the global variables to the record and Updates the table.

Listing 8.27 Setting the Record's Values and Updating the Table
 ' save all the configuration data gConfigRecord.Fields("InternetURL") = gstrInternetURL gConfigRecord.Fields("PublisherNetwork") = gstrPublisherNetwork gConfigRecord.Fields("Publisher") = gstrPublisher gConfigRecord.Fields("PublisherSecurityMode") = gPublisherSecurityMode gConfigRecord.Fields("PublisherLogin") = gstrPublisherLogin gConfigRecord.Fields("PublisherPassword") = gstrPublisherPassword gConfigRecord.Fields("Subscriber") = gstrSubscriber gConfigRecord.Fields("HostName") = gstrHostName gConfigRecord.Fields("Publication") = gstrPublication gConfigRecord.Fields("IPAddress") = gstrIPAddress gConfigRecord.Fields("PublisherDB") = gstrPublisherDatabase ' send the changes gConfigRecord.Update 

Listing 8.28 does the cleanup. If there are no errors, the return value is set to a success value of True. Also, the first time run flag is set to False now that you have the subscription synchronized.

Listing 8.28 Cleaning Up
 ' if we're done with no errors let's ' clear the first time flag and set ' return value to success If Err.Number = 0 Then     gblnFirstStartUp = False     SaveConfiguration = True End If 
Changing the CD Form and Supporting Functions for SQL Server CE Compatibility

The CD form frmCD needs some changes to be compatible with SQL Server CE. There are two key differences in frmCD for Pocket Access and SQL Server CE:

  • Because SQL Server CE subscriptions offer identity columns, you need to remove the manual primary key generating function, GetNewPK, and any calls made to it.

  • You need to modify any code that did in-place updates to recordsets created through SQL statements. (For more information about in-place updating, refer to "Updating SQL Server CE Recordsets" earlier in this chapter.)

Go ahead and remove the function GetNewPK from the project as well as any calls to it. Then, let's investigate the changes for SaveCD function in Listing 8.29.

Listing 8.29 frmCD.frm: Changes for SQL Server CE Compatibility
 Private Function SaveCD() As Boolean     Dim lngArtistId As Long     Dim lngGenreId As Long     On Error Resume Next     ' because we may have some cleanup     ' work to do, let's set the     ' default return value to true     SaveCD = True     ' do we have an existing or new genre     If Me.cboGenre.ListIndex >= 0 Then         ' existing genre         ' get the pk value         lngGenreId = Me.cboGenre.ItemData(Me.cboGenre.ListIndex)     Else         ' new genre         ' ask user to add new genre         If MsgBox("Would you like to add the genre: " _             & Me.cboGenre.Text, vbYesNo) = vbYes Then                 ' get a new genre pk value                 lngGenreId = AddGenre(Me.cboGenre.Text)                 ' is it valid?                 If lngGenreId < 0 Then                     ' outta here                     MsgBox "Error adding genre."                     SaveCD = False                     Exit Function                 End If         Else             ' can't save without a genre             SaveCD = False             MsgBox "You cannot save a CD without a genre." Exit Function         End If     End If     ' do we have an existing or new artist     If Me.cboArtist.ListIndex >= 0 Then         ' existing genre         ' get the pk value         lngArtistId = Me.cboArtist.ItemData(Me.cboArtist.ListIndex)     Else         ' new genre         ' ask user to add new genre         If MsgBox("Would you like to add the artist: " _             & Me.cboArtist.Text, vbYesNo) = vbYes Then              ' get a new artist pk value              lngArtistId = AddArtist(Me.cboArtist.Text, lngGenreId)              ' is it valid?              If lngArtistId < 0 Then                  ' outta here                  SaveCD = False                  MsgBox "Error adding artist."                  Exit Function             End If         Else             ' can't save without a artist             SaveCD = False             MsgBox "You cannot save a CD without a genre."             Exit Function         End If     End If     ' close the CD recordset, we're about to update     If gobjCD.State = adStateOpen Then         gobjCD.Close     End If     ' check mode     If Not IsNull(mlngCDId) Then  'edit mode - let's get the record   gobjCD.Open "CD", gobjConnection, _   adOpenForwardOnly, adLockOptimistic, adCmdTableDirect   '   set the index so we can seek   gobjCD.Index = "PK_CD"   '   seek the value   gobjCD.Seek mlngCDId  Else         ' new mode, no primary key value         gobjCD.Open "cd", gobjConnection, adOpenForwardOnly, _             adLockOptimistic, adCmdTableDirect         ' add the record         gobjCD.AddNew     End If     ' are we at the end of the     ' recordset (no record added     ' or no record found)     If gobjCD.EOF Then         Exit Function     End If     ' assign the values     gobjCD.Fields("ArtistID").Value = lngArtistId     gobjCD.Fields("CDName").Value = Me.txtCDName.Text     gobjCD.Fields("YearOfRelease").Value = CLng(Me.txtYear.Text)     gobjCD.Fields("Price").Value = CCur(Me.txtPrice.Text)     gobjCD.Fields("GenreID").Value = lngGenreId  '   set the UserId   gobjCD.Fields("UserId").Value = glngCurrentUser  ' update the recordset     gobjCD.Update     ' check for errors     If Err.Number <> 0 Then         Exit Function     End If     ' everything's cool, return true     SaveCD = True End Function 

Changes made to SaveCD are boldfaced in Listing 8.29. Let's investigate the first change in Listing 8.30.

Listing 8.30 Updating an Existing CD Record
 'edit mode - let's get the record gobjCD.Open "CD", gobjConnection, _     adOpenForwardOnly, adLockOptimistic, adCmdTableDirect ' set the index so we can seek gobjCD.Index = "PK_CD" ' seek the value gobjCD.Seek mlngCDId 

Listing 8.30 should accomplish finding a single CD record based on a primary key value. It uses the methodology to create an updateable recordset from SQL Server CE data. First, the recordset is opened using the adCmdTableDirect value for the Open method's Options parameter. Next, the Index property is set to the name of the primary key index for the table, allowing you to perform a Seek on the recordset. The Seek method is called using the primary key value for the record to update.

The next change to SaveCD is setting the UserId field's value for the current user. Having this value set allows the server side SQL Server to filter the CD table by users. The Public variable glngCurrentUser is set in the OpenDatabase function, shown in Listing 8.32.

 ' set the UserId gobjCD.Fields("UserId").Value = glngCurrentUser 

The two changes that take place to make CD Library work with SQL Server CE occur in the modGlobal.bas functions AddGenre and AddArtist. Listing 8.31 indicates boldfaced changes to AddGenre. AddArtist follows the exact same changes; if necessary, retrieve it from the sample source code.

Listing 8.31 modGlobal.bas: Changes for SQL Server CE Compatibility
 Function AddGenre(ByVal strGenre As String) As Long     ' adds a genre to the table and returns ' its primary key value     On Error Resume Next     ' set the return value to error code     ' in case we need to exit early     AddGenre = -1     ' if the recordset is open     ' let's check if it supports addnew     If gobjGenre.State = adStateOpen Then         ' if the current recordset does not         ' support adding new records, let's close         ' it and reopen it         If Not gobjGenre.Supports(adAddNew) Then             gobjGenre.Close         End If     End If     ' if the recordset is closed above     ' or closed before we get into this     ' function, let's open it with the     ' proper support for adding     If gobjGenre.State = adStateClosed Then  gobjGenre.Open "genre", gobjConnection, _   adOpenDynamic, adLockOptimistic, adCmdTableDirect  End If     ' add a new record     gobjGenre.AddNew     ' set the values     gobjGenre.Fields("GenreName").Value = strGenre     ' update the recordset     gobjGenre.Update  '   set the return value   gobjGenre.Close   gobjGenre.Open "SELECT MAX(GenreId) AS GenreId FROM Genre", _   gobjConnection, adOpenStatic, adLockOptimistic   AddGenre = CLng(gobjGenre.Fields("GenreId").Value)  End Function 

The GetNewPK function has been removed. In the first boldfaced change in Listing 8.31, the CursorType parameter for the Open method has been changed to adOpenDynamic. The second highlighted change made adds code to close the recordset, so it can be reused to find the identity value used for the record in the GenreId field. The identity value is retrieved using the MAX function, and then the return value is set from this result.

Let's look at the last supporting function for frmCD that was changed, OpenDatabase (see in Listing 8.32). OpenDatabase is modified to use the SQL Server CE ConnectionString syntax used in the Open method of the ADOCE.Connection gobjConnection. It also makes a call to SetUserId when it opens the database.

Listing 8.32 modGlobal.bas: Changes to the OpenDatabase Function for SQL Server CE Compatibility
 Function OpenDatabase() As Boolean     On Error Resume Next     ' set the return value to an error     ' condition, so we can exit early     ' if an error occurs     OpenDatabase = False     ' we need to check if the connection is     ' open or close, because EnsureDatabaseExists may     ' opened the database     If gobjConnection.State = adStateClosed Then  '   opens the SQL Server CE database   gobjConnection.Open "Provider=Microsoft.SQLServer.OLEDB.CE.1.0;" _   &   "Data Source=" & DB_PATH   '   if we're opening the db   '   let's set the UserId   If Not SetUserId() Then   Exit Function   End If  End If     If Err.Number = 0 Then         OpenDatabase = True     End If End Function 
modSQLRepl.bas: SetUserId, Setting the User ID

The SetUserId function opens the Users table and retrieves the single record in it. Remember, we used the HOST_NAME() function in the publication to filter down the Users table to one record. The Public variable glngCurrentUser is then set equal to the UserId field value. This variable is used when creating new CD records to specify which user they belong to.

Listing 8.33 modSQLRepl.bas: Knowing Which User Data Is Being Used
 Function SetUserId() As Boolean     On Error Resume Next     ' set to error condition     SetUserId = False     ' close the record set     If gobjUser.State = adStateClosed Then         gobjUser.Open "Users", gobjConnection, _             adOpenForwardOnly, adLockReadOnly, adCmdTableDirect    End If     ' if end of file, there's a problem     If gobjUser.EOF Then         Exit Function     End If     ' set the current user - used to populate UserId in cd table     glngCurrentUser = CLng(gobjUser.Fields("UserId").Value)     ' set the return value     SetUserId = Err.Number = 0 End Function 
frmMain: Wrapping Up the Last Changes

There are a few more changes made to the CD Library Replication application. First, remove the call to InitializeGlobals from the frmMain.Load event.

Next, add menu commands to the menu to call the synchronization routine and show the frmSQLProperties form.

The following Consts are added to the general section of frmMain to support the new menu commands:

 Const MENU_TOOLS_SYNC_KEY = "TOOLSSYNC" Const MENU_TOOLS_SETTINGS_KEY = "SETTINGS" 

In Listing 8.34, two highlighted lines of code are added to create a menu command for Sync and Settings. Next, in the MenuBar1.Menu_Click event, you respond to these new prompts.

Listing 8.34 frmMain.frm: Adding Menu Commands to the Menu for Sync and Settings
 Function PopulateMenus() As Boolean     Dim objMenu As MenuBarLib.MenuBarMenu     Dim objPrompt As MenuBarLib.Item     On Error Resume Next     PopulateMenus = False     ' add tools menu     Set objMenu = MenuBar1.Controls.AddMenu("Tools", MENU_ROOTPROMPT_TOOLS_KEY)  '   add Sync prompt   Set objPrompt = objMenu.Items.Add(, MENU_TOOLS_SYNC_KEY, "Sync")   '   add settings prompt   Set objPrompt = objMenu.Items.Add(, MENU_TOOLS_SETTINGS_KEY, "Settings")  ' check for errors, if none, return True     If Err.Number = 0 Then         PopulateMenus = True     End If End Function 

Listing 8.35 provides the new code to support the new menu commands. For the Sync menu command, the Sync method is called. After the synchronization is complete, the form is refreshed (there is potentially new data) by calling the PopulateArtistGrid function.

The Settings menu command displays the frmSQLProperties form by calling the Show method.

Listing 8.35 frmMain.frm: Adding Code to Respond to the New Menus
 Private Sub MenuBar1_MenuClick(ByVal Item As MenuBarLib.Item)     Select Case Item.Key  Case MENU_TOOLS_SYNC_KEY   Sync   PopulateArtistGrid   Case MENU_TOOLS_SETTINGS_KEY   frmSQLProperties.Show  Case Else             MsgBox "Unknown Menu!"     End Select End Sub 

Team-Fly    
Top
 


eMbedded Visual BasicR. WindowsR CE and Pocket PC Mobile Applications
eMbedded Visual BasicR. WindowsR CE and Pocket PC Mobile Applications
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 108

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