Chapter 6. Multiuser Issues

Hacks 55–58

You can deploy Access databases as standalone applications as well as in shared systems. Although working with a shared database provides many benefits in terms of efficiency, issues can crop up with regard to users’ activities getting in the way of each other’s data. This chapter provides a few workarounds for integrating Access in a multiuser environment while ensuring data doesn’t get trampled. “Build a Time-Out Feature” [Hack #57] catches and completes idle record edits, thereby allowing others to make changes. “Test for Duplication” [Hack #55] shows a way to validate data before users duplicate each other’s entries. The chapter also covers a distribution method [Hack #56] that makes it easy to get a split database from your development machine to your clients, with the table links already matching the network.

Test for Duplication

Before you insert multiple entries into master tables in a busy data-entry environment, you’ll need a custom validation process to avoid duplicated data.

Just because a database is deployed on the server doesn’t mean the entire application must be in that server copy. A common approach is to put the data in the server database and distribute the forms to the local client computers, inside another Access file. This is a typical Access version of a client/server application.

Because the client installations are Access databases, using tables in the client databases opens up possibilities. One useful technique is to have new data entries go into local tables first, and later to bulk-insert them into the master table or tables on the server.

The heart of this technique is that entry operators have local tables that mirror the server tables. Their forms are bound to the local tables and all entry is done locally. At the end of the day, or at scheduled times throughout the day, a process runs that takes the data out of the local tables and moves it to the server tables.

Here is where the advantage lies. The insert process gathers the input from all the entry operators’ client tables and tests for duplication before the actual insert into the server tables. Normally, such an interim validation would be overkill because data usually is validated upon entry. However, the point here isn’t really to validate data in terms of correct content, but rather, to see if duplicate records were entered during input.

This is certainly a possibility in a busy customer service, sales, or telemarketing operation. For example, in an environment where phone calls come in, it is possible that Jane places an order for something and, an hour later, her husband Joe places an order for the same item. If different operators handled the husband and wife, no one would be the wiser that this is a duplicate order. Even the two created records might not be actual duplicates because the first name is different in each record. But if a custom-designed validation process is used, these two records can be flagged as duplicates because at least the address is the same in both records.

You also can test for near duplication on the server, so this begs the question: why bother with the separate table-entry approach? The answer is performance. If all entry goes straight to the server tables, and the custom duplication process runs on the larger tables, there could be some issues with speed.

Another issue to consider is how far back in time to look for duplicates. With the local approach, the test certainly is done at least at the end of the day, if not during scheduled times throughout the day, all for that day’s processing. Involving older records in the server tables isn’t necessary. An order placed twice in one day is probably a duplicate. An order that resembles one placed last week or last month is probably a repeat order.

—Andrea Moss

Distribute a Split Database with Predefined Table Links

If you follow this interesting distribution game plan, users will not have to link their local database files to the data tables on the system.

The technique known as database splitting, which involves a tables-only back-end Access file on a network share, copies of front-end Access files (with forms, reports, and so on) on each user’s C: drive, and the use of linked tables, has been around for quite some time. The benefits of such client/server database configurations are widely known and documented.

However, it can be a challenge to deal with split databases during periods of frequent updates, especially during the development phase. For some users, their Office installation doesn’t even include the Linked Table Manager, so they can get prompted for the Office installation CD when they attempt to refresh and change links. Other users might simply be uncomfortable or unfamiliar with how linked tables work. Frequent relinking, especially for users who were used to just sharing an MDB from one location, can be problematic.

When frequent rounds of revisions are being submitted to end users during the initial prototyping stage of development, it makes sense to keep the project in only one file. But once the data model is signed off, it’s time to split the database. You can do this in two ways:

  • Make a copy of the database so that you have two identical copies. In one copy, delete the tables. In the other copy, delete everything but the tables.

  • Use the Database Splitter utility (Tools Database Utilities). This automatically creates an Access file with just the tables and, at the same time, removes the tables from the database running the utility.

Then the back-end database (the one with the tables) goes on a network share, while the front-end database is distributed to users and is run from their PCs. The problem is that the front-end database must be linked to the tables in the back-end database. Simulating these links on your development PC before distributing the front end is the point here. If you can set the links so that they are the same as those in the production environment, users will not have to deal with establishing the links themselves from their PCs.

In other words, you can distribute the front end prelinked. All of this is based on the assumption that a drive-mapping standard is in place and that all users will have an identical map path.

Copying the Network Drive to Your Development Machine

The SUBST DOS command is all you need to copy the network drive to your development machine. On your development machine, you can use SUBST to create a map that matches the one users need.

First, create a directory on your computer that matches the folder on the share where the back-end database will go. If the network path includes subdirectories, create a path that matches that path structure on your development machine.

The syntax for using SUBST requires the new drive letter and the path it is set to, like this:

	SUBST <New Virtual Drive Letter:> <Path to map to that letter>

For example, if you have a subfolder named XYZ_Corp in your C:Clients folder, and you want to map that folder to an S: drive, click Start/Run; type command (Windows 98, Me) or CMD (Windows NT, 2000, XP); click OK; and enter this at the command line:

	SUBST S: C:ClientsXYZ_Corp

Figure 6-1 shows how you do this in the Command Prompt box.

Using SUBST
Figure 6-1. Using SUBST

If users are accessing a subfolder under the S: drive, create matching folder names under the folder that was substituted to the S: drive. Place the back-end database in the appropriate folder.

Now, when you are in the front-end database file and are linking the back-end tables, browse to your new S: drive to find the back-end database, thereby keeping the links the same as what the users need. When you send your users an update, they should not have to relink anything. This new drive letter will even show up when you open My Computer. The new virtual drive letter will last until the next time you restart. If you decide you no longer need a virtual drive, you can get rid of it with the /d switch:

	SUBST S: /d

It’s important to note that this removes the virtual mapping; it doesn’t delete the folder or its files. Also, you can’t use SUBST if you already have a drive using that letter, so if your keychain flash drive is using G:, you have to safely remove it before SUBSTing a folder to use the G: drive.

Of course, this technique works only in extremely stable environments, where all users have the same drive letter mapped to the given share. Although S: might work for your primary contact, other users of the application might have a different letter mapped to that location, or they might not have any letter mapped. They could be accessing the folder through its Universal Naming Convention (UNC) name (\ServerNameShareName). If this is the case, you can emulate this on your PC as well as long as you are willing to rename your PC to match the server’s name (you can always change it back later).

Using UNC Instead

If you want to use UNC instead, you need to rename your computer. First, you need to know the server name at the client site and the full path of folders and subfolders to the share that will hold your back-end datafile. To rename your computer to match the server, bring up your PC’s System Properties by right-clicking My Computer and clicking Properties, or by double-clicking the System icon in the Control Panel to open the System Properties dialog box. Select the Computer Name tab, and then click the Change button. When you see the Computer Name Changes dialog box, shown in Figure 6-2, type the desired name for the computer. It will require a reboot to take effect. Of course, this assumes you will be creating a name conflict on the network. The assumption is that your development machine isn’t on the production network. If it is, you can disconnect your computer during this process.

Changing the name of the computer
Figure 6-2. Changing the name of the computer

So, if XYZ Corp.’s server, called ServerName, has a DeptShare folder and a subfolder called DataFolder that will hold your datafile, change your computer’s name to ServerName. Then, create a folder named DeptShare off the root of your C: drive, and create a subfolder called DataFolder inside the DeptShare folder.

Once the folder structure is in place, browse to the DeptShare folder, rightclick in an empty area of the folder, and then click Properties. Select the Sharing tab, and make selections to share the folder, as shown in Figure 6-3.

Sharing a folder
Figure 6-3. Sharing a folder

Now go to My Network Places, and click Add a Network Place. Click Next on the wizard’s first screen, and the wizard will ask you where to create the new network place; select “Choose another network location.” Click Next, and in the Internet or Network Address box, type \ServerNameDeptShare. Click Next; Windows will ask what to call the share. If the name isn’t already in the box, type DeptShare for ServerName. Click OK, and then click Finish.

Finally, back in your Access application file, delete all the linked tables. This time, when relinking the tables, make sure to go through My Network Places/Entire Network in the link dialog to browse to the datafile, or type \servernamedeptshare into the dialog to browse to the datafile. This causes Access to create the links to use the UNC naming convention. If you use the My Computer shortcut to your share, Access recognizes that it is local and uses the C: drive path to create the link. To ensure that your link is using UNC, type this in the debug window:

	?Currentdb.TableDefs("<your table name>").Connect

Make sure to put the name of one of the linked tables in the code line where you see the <your table name> prompt. The response should look like this:

	;DATABASE=\ServernameDeptShareDataFolderProject_dat.mdb

Note that you will see the name of your database; you won’t see Project_ dat.mdb. If you get the following response, you need to try again, making sure you go through the entire network, workgroup, computer name, and share name when browsing to your datafile:

	;DATABASE=C:DeptShareDataFolderProject_dat.mdb

Once this is correct, end users at the client site shouldn’t need to relink, regardless of which drive letter (if any) they have mapped to the network location.

—Steve Conklin

Build a Time-Out Feature

Make sure your data is saved and available to others. Lock the records when they’re not being updated.

The phone rings, or you are late to a meeting, or any number of other distractions pop up. It happens to all of us. Unfortunately, you sometimes forget to close out of the file open on your PC.

In a multiuser database, this can be a real nuisance. Depending on the record-locking scheme being used, if a record is left in the middle of an edit, other workers might not be able to make changes to that record. Figure 6-4 shows the dreadful message a user can get when attempting to make a change to a record someone else has left open.

A record that has been left in an edited state
Figure 6-4. A record that has been left in an edited state

Although the message in Figure 6-4 gives the second user the options he needs, it is better to not even see this message, if it can be avoided. A productive measure for this situation is to close a form in which no activity is sensed after a period of time. In other words, if the first user has not completed any changes to the record within a specified time, the form should just close. Closing the form ends the record-editing process, and the changes are saved automatically. The alternative, to drop the changes, is discussed at the end of this hack.

It’s About Time

Forms have an intrinsic timer control and Timer event. If you’re familiar with Visual Basic, you know how to actually place a timer control on a form. In Access, the control is effectively already on the form, although you don’t see it. Instead, you use the property sheet to set the Interval property and to indicate what occurs in the On Timer event.

To display the property sheet, open the form in Design mode, and press F4 on the keyboard. If necessary, make sure the property sheet is displaying properties about the form itself, not about one of the controls or sections. Select Form from the drop-down box at the top of the property sheet.

Figure 6-5 shows the property sheet set to display the properties for the form. The On Timer and Timer Interval properties are found on both the Event tab and the All tab.

Tip

You can display the property sheet in a few ways. You can press F4, or you can press Alt-Enter. You can also use the View Properties menu, or just click the Properties button on the Form Design toolbar.

The Interval property accepts values between 0 and 2,147,483,647 milliseconds. A setting of 1000 equals one second. The 10000 setting shown in Figure 6-5 is the equivalent of 10 seconds. By the way, the largest setting of 2,147,483,647 equals almost 25 days. Yes, you can schedule an Access event every 25 days!

The On Timer event property links to either a macro or a code procedure. In this example, a code procedure was written. I’ll explain the code soon, but first, let’s examine the form’s design.

In Good Form

Figure 6-6 illustrates the form design, field list, and property sheet. Notice the text box control in the form header that isn’t bound to a field. The property sheet is set to display the properties of the unbound box, txtTime, and its Visible property is set to No. In other words, when the form is in View mode, the txtTime text box won’t be seen.

Setting the Timer Interval and On Timer event
Figure 6-5. Setting the Timer Interval and On Timer event
An unbound text box to hold a time reference
Figure 6-6. An unbound text box to hold a time reference

The txtTime text box isn’t meant to be used for entry. Instead, it holds a snapshot of the computer’s clock time, at the moment the form is activated. To make this happen, you need to enter a little code in the form’s Activate event.

To get to the event code stub, select [Event Procedure] from the drop-down menu to the right of On Activate in the property sheet and then click the ellipses (…) button, as shown in Figure 6-7. This brings you to the form’s code module, right at the start of the Activate event. How’s that for convenience?

Getting to an event code stub from the property sheet
Figure 6-7. Getting to an event code stub from the property sheet

Here is the code to enter in the Activate event:

	Private Sub Form_Activate( )
       Me.txtTime = Now
	End Sub

The On Timer event contains the DateDiff function, set to test for the difference in seconds between the established form activation time and the current time. From the property sheet, select [Event Procedure] in the drop-down menu to the right of On Timer. Click the ellipses button and enter this code:

	Private Sub Form_Timer( )
	If DateDiff("s", Me.txtTime, Now) > 5 Then
	   DoCmd.Close
	End If
	End Sub

The first parameter of the DateDiff function indicates which interval to test; in this case, s is for seconds. The function tests if more than five seconds have lapsed between the time stored in the txtTime text box and now. Bear in mind that there are two values to coordinate here: the timer interval and how many seconds to test for.

This example is set up to test every 10 seconds if there is a difference of five seconds, but you can change these numbers. For example, it might be easier on the user if the timer interval is 30 seconds. There is a balance of what makes sense here. If users are likely to edit the same records often, make the interval shorter.

Tip

The Now() function returns the system time. Every computer keeps an internal clock running. When timing events, it’s necessary to start with a baseline time. The Now() function takes a snapshot of the time, which is then compared to a later time (effectively another snapshot, but later in time). Subtracting the first snapshot from the second snapshot equals the elapsed time. Incidentally, the computer clock is also used in programs that allow you to enter “today’s date.” Sometimes, the clock needs to be reset.

If we stopped here, the form would close 10 seconds after being opened. That is, upon the first run of the On Timer event (which occurs 10 seconds after the form is opened) a difference greater than five seconds is found, and the DoCmd.Close line runs, closing the form. But our goal is to close the form only when there is no activity, not just for the heck of it.

The key to making this hack work is to add code to each change event for the various text entry boxes on the form. The form in this example has text boxes for editing employee name, department, title, and so on. The Change event for each text box receives a single line of code to update the txtTime text box with the current time. In other words, every time a change is made in an entry text box the txtTime text box (remember, this one is invisible) is reset to Now, like this:

	Private Sub Department_Change( )
       Me.txtTime = Now
	End Sub

The Change event fires each time a character is entered or backspaced out of the text box. Therefore, as a user types in one of the entry text boxes, the txtTime text box is constantly updated with the current time. Then, when the timer event fires, the DateDiff function returns a difference of less than five seconds, and the form stays open. Only when the form is left idle does a difference greater than five seconds occur, thereby closing the form.

Figure 6-8 shows how the form’s code module should look after these routines have been entered. It’s OK if your event routines aren’t in the same order.

The code that handles an inactive form
Figure 6-8. The code that handles an inactive form

Hacking the Hack

You can implement this hack in many different ways. So far, all we know is how to reset the baseline time each time a character is entered with the keyboard. Also, the only action after a period of inactivity has been to close the form. Here are some other ideas.

Reset the time when the mouse is moved.

In addition to capturing keyboard entries as a way to reset the time held in the invisible text box, it makes sense to do this whenever the mouse is moved as well. Some people are quick on the mouse, and just giving the mouse a push keeps the form open. In fact, I often do this to keep my screensaver from starting up.

Access forms can also use the MouseMove event. Insert code into the MouseMove event in the same manner explained earlier. The purpose of the code is the same, to reset the invisible text box to Now .

	Private Sub Title_MouseMove(Button As Integer, _
         Shift As Integer, X As Single, Y As Single)
       Me.txtTime = Now
    End Sub

As long as the mouse is moved at least once every 10 seconds, the form will stay open.

Let the user decide the timer interval.

Each user has his own way of working, not to mention his own speed of working. So, instead of hardcoding the timer’s interval value, why not let the user decide what is best? To do this, you have to build a way to let him select the interval into the form (or somewhere else, such as in a preferences area). Figure 6-9 shows how the form has been modified by adding a combo box. The combo box lets the user select from a list of possible values.

Letting the user decide how long to wait before closing the form
Figure 6-9. Letting the user decide how long to wait before closing the form

The code is updated as well. The combo box has been named cmbSeconds. Its Row Source Type is set to Value List, and the Row Source is set to the choices 10, 20, 30, 40, 50 , and 60 . When the user selects a value from the combo box, the combo box’s Change event fires to update the invisible text box to the current time. Also, the form’s Activate event now takes care of establishing a default time to wait—20 seconds in this case, as shown in Figure 6-10.

It’s necessary to have a default value to use until or unless the user selects an interval. Finally, the number of elapsed seconds that are tested for is now always one fewer than the interval selected in the combo box. Figure 6-10 shows the updated code module.

Setting the Interval property with the combo box Change event
Figure 6-10. Setting the Interval property with the combo box Change event

Save the record but leave the form open.

Just because a record is displayed in a form doesn’t necessarily mean it is being edited. The Dirty property is true if edits have been made or false if no data has changed. You can change the code in the form’s Timer event to test the Dirty property. If it is true , the record is saved, and a message is presented, as shown in Figure 6-11. If Dirty is false, and no edit is occurring, nothing happens. Either way the form stays open.

The new code for the Timer event uses a pair of nested If statements. First, if the elapsed time is greater than the predetermined interval of the test, the second If statement comes into play. The second If tests the Dirty property. If true , the record is saved, and the message is displayed:

The saved-edits message
Figure 6-11. The saved-edits message
	Private Sub Form_Timer( )
	If DateDiff("s", Me.txtTime, Now) > Me.cmbSeconds - 1 Then
      If Me.Dirty Then
		DoCmd.RunCommand acCmdSaveRecord
		MsgBox "Edits have been saved!"
      End If
	End If
	End Sub

If you started an edit and didn’t complete it, the code completes it for you. If an edit is initiated, no harm is done.

Close the form without saving the record.

So far, each approach in the hack has been based on saving the record that is in the middle of an edit. This is a judgment call because even if the person walked away from his work you don’t know for sure whether to save his entry. To be on the safe side, the work is saved.

Of course, the argument exists to not save the edits. It’s easy to drop the edits with an Undo action. Here is a snippet of modified code that goes into the Timer event:

	Private Sub Form_Timer( )
	If DateDiff("s", Me.txtTime, Now) > 10 Then
	  If Me.Dirty Then
	    DoCmd.RunCommand acCmdUndo
		DoCmd.Close
      End If
	End If
	End Sub

Essentially, if the record isn’t in its pristine state (confirmed by the Dirty property), the code runs an Undo command and closes the form without saving the record. This is just one way to handle dropping a half-completed edit.

Another enhancement is to save the values out of the form and into a temporary table or even a text file, and to leave a message box alerting the user that his entry was dropped but that he can find his unsaved efforts at the place where you saved them.

Implement Unique Usernames

Even when Access Security isn’t active, you can implement unique usernames when all users are Admin.

Access Security is great in many multiuser situations because you can assign rights to groups and individuals. However, sometimes this is just a lot of overhead. In a small group of users who all use the same objects, you don’t get much added value by implementing security.

The downside of not using security is that all users are given the name Admin. You can confirm this in an unsecured database by going to the Immediate window (Ctrl-G in the VB Editor) and typing the following:

	?CurrentUser

The CurrentUser property contains the logged-in name of the user. When security is on, each user has a specific name. When security is off, all users are Admin.

An easy way to use specific names in an unsecured database is to first have users enter their names and then have the entered names available throughout the session. This technique makes sense only in a configuration in which each user works on a local database with the forms. The data tables remain on in a back-end database on the server.

When a user starts up her client-based front end, she is asked to enter her name. This is just an easy affair handled by an input box. A loop keeps testing for her entry, and when she is done, the entry is handed off to the Tag property of the main form. This works best if the main form opens automatically when the user starts up the database. This code goes into the main form’s Open event:

	Private Sub Form_Open(Cancel As Integer)
		Dim user_name As String
		user_name = ""
		Do Until user_name <> ""
          user_name = InputBox("Please enter your name", "Enter Name")
        Loop
		Me.Tag = user_name
    End Sub

Throughout the session, the person’s name is always available via this simple reference back to the main form and its tag, assuming that the main form is named frmMain. Change the form name to match yours:

	Forms!frmMain.Tag

Because the application is configured in the way that each user is using a local version of the main form, the reference to the Tag property always returns the user’s unique name.

That’s all it takes. By accessing the entered username in this way, you can use the name in reports, populate field text boxes with it, use it in queries; in other words, use the name wherever you need it in your application.

—Andrea Moss

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.91.37