Professional UML with Visual Studio. NET. Unmasking Visio for Enterprise Architects

Chapter 3 - Diagramming Business Objects
byAndrew Filevet al.?
Wrox Press ©2002

Team FLY

Later in this chapter we're going to create business objects that are specific to a particular application. However, before we get to that point, we need to design business objects and data-access base classes to use for deriving our application-specific business objects.

Although the .NET Framework class library contains many great classes, there is no business object base class. This means you'll have to roll your own or buy a third-party business component. In either case, there is some important functionality you should expect from a business object base class.

As menti oned earlier, a business component should be able to access different types of data. A good way to accomplish this is by placing all data access logic in data-access classes rather than in the business object itself. When a business object gets a request for data, it can pass the call to a data-specific data-access object that contains the actual data-access logic. We can create a family of data-specific classes for accessing SQL Server, Oracle, and so on. This allows us to attach the data-access classes we need to the business object at run time.

We'll take you step by step through the process of modeling these data access classes in Visio to show you how it's done. We'll go into greater detail creating these first classes, but later in the chapter we'll assume you are familiar with the process described here.

To begin, launch Visio and create a new UML Model Diagram. This opens a new, blank diagram in the Visio IDE and displays a new Top Package node under the Static Model node of the Model Explorer as shown in the following image.

The Model Explorer displays your UML model as a hierarchical tree view. As described in the next section, folders in this tree view can correspond to a namespace hierarchy. You can add diagrams, sub-packages, subsystems, classes, interfaces, data types, actors, and use cases diagrams to a package. Each of these elements is represented by a different icon in the tree view. You can right-click on these elements to delete, rename, and edit them, or you can drag and drop them on various diagrams.

In the next section, we will create packages representing namespaces in .NET.

Creating Namespace Packages

In .NET, namespaces provide a means for categorizing your classes and creating unique class names. In Visio, packages perform a similar function. Packages, represented as folders in the Model Explorer, can be used to represent namespace hierarchies. In the case of our data-access classes, we'll create a package structure representing a Wrox.UMLDotNet.Data namespace. According to Microsoft's naming convention, the first part of a namespace should be your company name (we'll use Wrox for this example), the next part should be your product (this book, UMLDotNet), and then any additional name segments to further categorize your class. To create this package structure in Visio:

  1. Right-click the Top Package node and select New | Package from the context menu. This launches the UML Package Properties dialog shown in the following image.

  2. In the Name text box, enter Wrox, and then click OK. This adds a new Wrox package node beneath the Top Package node.

  3. Right-click the new Wrox package and perform the same steps to add a new UMLDotNet node.

  4. Add a new Data package beneath the UMLDotNet package. This time add the following text to the Documentation box:

    The Wrox.UMLDotnet.Data package contains a family of data access classes used to access a variety of data using .NET managed data providers.

Documentation is critical to a good object model. Regardless of how descriptive your package, class, and member names are, you need to document your intent for those who read and interpret your model. Although your design may seem self-evident at that time you create it, a few weeks or a few months later I guarantee that good comments will save you hours of rethinking and head scratching!

  1. When you're done, click OK, and the packages in the Model Explorer look like the following diagram.

Creating an Abstract Data-Access Class

Because there are a variety of ways to access data, it makes sense to model an abstract data access base class defining an interface for a family of data access classes. We can then model concrete subclasses that access specific kinds of data.

To create a new class belonging to the Wrox.UMLDotNet.Data namespace, add the class to the Wrox | UMLDotNet | Data package in Visio:

  1. Right-click the Data package in the Model Explorer, and select New | Class from the context menu. This launches the UML Class Properties dialog that allows you to specify a variety of class attributes.

  2. As shown in the following image, enter DataAccessBase in the class Name text box. To specify that the class is abstract, select the IsAbstract checkbox. You can also enter a description of the class in the Documentation box.

  3. When you're finished, click OK to close the dialog.

When the dialog closes, look in the Model Explorer and you will see a new DataAccessBase class node as shown in the following screenshot. We can see that the classes added to a package belong to the namespace represented by the package hierarchy.

Creating a Class (Static Structure) Diagram

Next, let's create a new class diagram beneath the Top Package node to display the classes we create. Class diagrams are a type of static structure diagrams that provide visual representations of your classes. They allow you to model class attributes, operations, associations, interfaces, and dependencies.

  1. Right-click the Top Package node and select New | Static Structure Diagram from the context menu (a class diagram is a type of static structure diagram). This creates a new static structure diagram and opens it in the Visio IDE. It also adds a new Static Structure-1 node beneath the Static Model node as shown in the following diagram.

  2. Rename the class diagram node to Business and Data Classes by right-clicking the Static Structure-1 node, selecting Rename from the context menu, and entering the new name in the node edit box. You should see the new title of the class diagram displayed in the Visio title bar.

  3. I usually turn off grid lines and connection points on the document to make it easier to see the class in screenshots. If you want to turn these off too, from the Visio main menu, select View | Grid, and then View | Connection Points. When you turn these off, the corresponding menu items are unchecked.

  4. Drag the DataAccessBase class from the Model Explorer and drop it on the class diagram. It should look like the following image:

A few things to note - the class name is shown in italics. The UML specification dictates that abstract class names be displayed in italics. The Data: prefix indicates this class is found in the Data package.

This is a good time to save your Visio UML Model. Select File | Save from the menu to display the Save As dialog. Specify the name you want to give this file and the directory where you want to save it. If this is the first time you've saved the diagram, a Properties dialog displays prompting you to specify information about your model. In the Summary tab, you can specify information such as a title, subject, author, manager, and company. You can also specify a relative path for hyperlinks and how you want to display a preview of the model. The Contents tab displays each page of the model and the master shapes on them. For more information on this dialog, click the Help icon at the bottom left corner of the dialog. For now, just change the Title to My Sample Model, and then click the OK button.

Adding Operations to the Class

Now let's add a few basic operations to the DataAccessBase class. An operation in a UML class is implemented as a method in a real-world .NET class, as shown in the table earlier in this chapter. In Visual Basic .NET, operations that do not return a value are implemented as subroutines (Sub), whereas operations that do return a value are implemented as Functions. In C#, there is only one kind of method - methods that do not return a value simply return void.

This isn't a study in data access, so we won't go into too much detail here. We'll just add a few operations allowing us to retrieve and save data in a DataSet. Both operations will accept a database key used to look up database connection information in a configuration file:

  • GetDataSet - Accepts a SQL command string parameter and a database key. Returns an untyped DataSet.

  • SaveDataSet - Accepts a DataSet parameter and a database key. Returns an integer indicating the number of records updated.

In a production data access class, we would also add a number of methods to delete records, work with stored procedures, and typed DataSet objects. For this example, the two methods shown above are sufficient.

Now let's add these two operations to our abstract DataAccessBase class. The implementation of these operations is different for each concrete subclass (there is different code in the methods of each subclass), so we'll make them abstract. This also guarantees any future subclasses will implement these operations, because .NET compilers enforce implementation of inherited abstract methods.

The UML Class Properties Dialog

In Visio, you add a new operation to a class by using either the UML Class Properties dialog or the UML Operation Properties dialog (discussed in the next section). To launch the UML Class Properties dialog, you either right-click the DataAccessBase node in the Model Explorer or right-click the class on the static structure diagram and select Properties from the context menu. When the dialog appears, select the Operations item in the Categories pane on the left and the Operations grid is displayed on the right as shown in the following screenshot - from here we will be able to add new operations to a class.

Here is a description of each column in the Operations grid:

Column

Description

Default Value

Operation

The name of the operation.

operation1

Return Type

This column contains a combo box listing all classes in your model as well as the basic C#, C++ .NET, IDL, and VB.NET data types.

<None>

Visibility

This column's combo box has three options - public, protected, and private. There are two visibility options missing - internal and protected internal in C#, and the equivalent Friend and Protected Friend in VB.NET, so unfortunately, there is no way to specify these options.

public

Polymorphic

Specifies if the operation can be overridden by subclasses. Select this checkbox if the operation is virtual in C# or Overridable in VB.NET.

unchecked

Scope

There are two options in this column - classifier and instance. Classifier is equivalent to static in C# and Shared in VB.NET. Instance is the same as instance methods in both C# and VB.NET.

instance

The UML Operation Properties Dialog

Although this dialog can be used to add new operations, it doesn't include entry fields for some very basic operator information such as a description and parameters. This is where the UML Operation Properties dialog comes to the rescue. To launch this dialog, click the New button from the UML Class Properties dialog, which adds a new record to the Operations grid, and then click the Properties button. As you can see in the following image, this dialog allows you to specify more information than the previous dialog to better define your class operations.

Here is a description of each entry field in this dialog (some of these fields are the same as those in the Operations grid described previously):

Column

Description

Default Value

Name

The name of the operation.

operation1

Stereotype

Operation stereotype. You can add your own custom stereotypes that appear in this combo box by selecting UML | Stereotypes from the main menu.

<no stereotypes>

Prefix

Allows you to specify a language-specific prefix to further identify an operation return type. This box is disabled if the operation return type is set to <None>.

 

Suffix

Allows you to specify a language-specific suffix to further identify an operation return type. This box is disabled if the operation return type is set to <None>.

 

Return Type

This combo box lists all classes in your model as well as the basic C#, C++ .NET, IDL, and VB .NET data types.

<None>

Expression

A read-only field that displays the concatenated prefix, return type, and suffix values you have specified.

 

Visibility

This combo box has three options - public, protected, and private. There are two visibility options missing - internal and protected internal in C#, and the equivalent Friend and Protected Friend in VB .NET, so unfortunately, there is no way to specify these options.

public

IsPolymorphic

Specifies if the operation can be overridden by subclasses. Select this checkbox if the operation is virtual in C# or Overridable in VB .NET.

unchecked

IsQuery

Specifies if the application of the operation changes the state of its element. Select this checkbox if running the operation changes the state of an object. For example, a Contract business object may have an Accept method that changes the state of the contract from 'pending' to 'accepted'.

unchecked

OwnerScope

There are two options in this combo box - Classifier and Instance. Classifier is equivalent to static in C# and Shared in VB .NET. Instance is the same as instance methods in both C# and VB .NET.

instance

Call Concurrency

There are three options in this combo box - Concurrent, Guarded, and Sequential. Concurrent describes how an operation acts in the presence of multiple threads of execution. The Concurrent setting means multiple concurrent calls can occur at the same time and proceed concurrently. The Guarded setting specifies multiple calls can be made from concurrent threads, but only one call is processed at a time and others are blocked until the current operation completes. The Sequential setting means only one thread can execute in the operation at a time.

sequential

Follow these steps to add a new GetDataSet operation to the DataAccessBase class:

  1. Enter GetDataSet in the Name field. When you enter this name, it is immediately displayed in the Model Explorer.

  2. Navigate to the Return type combo box. If you open the list, you see a list of all classes in the model, as well as .NET data types. At this point, the only custom class you'll see is the DataAccessBase class. As mentioned earlier, this method is intended to return a DataSet, however, because the DataSet class is not in the model, select <unspecified> from the list.

  3. Leave Visibility set to public. Check the IsPolymorphic checkbox, because an abstract operation must be polymorphic. Leave the IsQuery checkbox unchecked.

  4. The OwnerScope combo box is equivalent to the Owner combo box on the previous dialog. Leave it set to instance.

  5. Leave the Call concurrency combo box set to sequential.

  6. Add the following text to the Documentation box:

    Retrieves a DataSet containing a result set for the specified SQL SELECT string.

You should always specify a description for all classes and members so developers using your classes can know your intent!

Specifying Operation Parameters

Now let's add parameters to the operation. Previously, we determined this method accepts two string parameters-a SQL SELECT string and a database key.

  1. In the Categories pane, select Parameters. This displays the Parameters grid on the right side of the dialog as shown in the following image.

The Parameters grid allows you to view and specify operation parameters. You may be surprised to see a parameter already shown in the grid. If you look in the Kind column, you can see that this is the return value that we specified for this operation.

  1. Click the New button to add a new parameter to the grid, and then click the Properties... button to edit the new parameter record. This launches the UML Parameter Properties dialog as shown in the following image, from which we can specify operation parameters.

  2. In the Name textbox, enter command as the name of the parameter.

  3. In the Type combo box, select one of the string data types.

In the Kind combo box, there are three choices - in, out, and inout.

  • The in option means that if the parameter value is changed in the method, the change does not affect the caller. This is also known as passing a parameter by value.

  • The out option specifies the parameter value is passed from the method back to the caller. This is similar to C#'s out parameters.

  • The inout option means the parameter is passed by the caller, can be changed by the method, and the caller sees this change. This is also known as passing a parameter by reference.

Leave the Kind combo box set to in.

  1. In the Documentation box, enter Specifies a SQL SELECT command to be executed.

  2. Click OK to close the dialog. This takes you back to the UML Operation Properties dialog, displaying the new parameter in the Parameters grid.

  3. Add another parameter to the operation by clicking the New button, and then clicking the Properties button.

  4. In the UML Parameter Properties dialog, set the Name to databaseKey.

  5. In the Type combo box, select one of the string types.

  6. In the Documentation box, enter:

    Specifies a lookup key for the database in the application configuration file

    and then click OK to close the dialog.

You should now see three parameters for the operation as shown in the following image. Note that both the operation return values and parameters are displayed:

Marking an Operation as Abstract

As we mentioned earlier, the GetDataSet method is supposed to be abstract. To mark it as such, select Method in the Categories pane to display the controls shown in the following image.

This brings up the question of what the difference is between an operation and a method. When you implement classes in code, methods are added to the class for every operation specified in the class model.

When it's on a diagram, it's an operation; when it's implemented as code in a real-world class, it's a method.

The following table shows how different basic UML class elements map to .NET code:

UML Element

C#

VB .NET

Class

Class (a.k.a. Type)

Class (a.k.a. Type)

Operation

Method

Subroutine or Procedure (see the entry for "Return value" below)

Attribute

Field

Member variable

Parameter

Parameter

Parameter

Return value

Return value

Return value. Operations that do not return a value are implemented as Subroutines. Operations that return a value are implemented as Procedures.

Unlike concrete operations, abstract operations have no corresponding concrete method, just a method signature, which includes the method name, its parameters, and their types.

The Has method checkbox is selected by default, indicating that the operation selected in the Operation name combo box will have a corresponding method in the realized class. Because this is an abstract operation that will not have a corresponding method containing code, let's uncheck this box. When you clear this checkbox, the Language and Method body fields are disabled indicating there is no code associated with this operation. If this box is unchecked, when you generate code from the class, the resulting method is marked as abstract.

Click OK to close the UML Operation Properties dialog, and then click OK again to close the UML Class Properties dialog. The visual representation of your DataAccessBase class should look like the following image:

Notice the GetDataSet method is shown in italics per the UML specification for abstract operations. The class diagram shows the GetDataSet operation signature, including the parameter names and their types. In the next section, we'll take care of the method return value being marked unspecified.

Adding .NET Base Classes to the Model

There isn't a DataSet class in our model, so we weren't able to specify the GetDataSet operation returns a DataSet. If you don't have .NET base classes in your model, you're not able to specify the base classes from which your custom classes are derived, and you can't specify parameters and return values that are .NET base class types. Although we discuss an interesting way around this problem in Chapter 5, for the purposes of this chapter, let's address this problem by adding a DataSet class to our model. The DataSet class belongs to the System.Data namespace, so we need to add System | Data packages to the Top Package node in our diagram.

  1. Right-click the Top Package node and select New | Package from the context menu. This launches the UML Package Properties dialog.

  2. In the Name text box, enter System, and then click OK.

  3. Right-click the System package and add a new Data package beneath it.

  4. Right-click the Data package and select New | Class from the context menu.

  5. In the UML Class Properties dialog, enter DataSet in the Name text box and click OK to save changes.

When you're done, your model tree should look like the following diagram.

Now that we have a DataSet class, we can go back into the definition of the GetDataSet method and specify a return value of DataSet. The easiest way to do this is to expand the DataAccessBase node in the Model Explorer, and double-click the GetDataSet operation. This launches the UML Operation Properties dialog with the GetDataSet operation ready for editing. In the Return type combo box, select Data::DataSet, and click OK to close the dialog.

Adding the SaveDataSet Operation

Now, let's add our second SaveDataSet operation to the DataAccessBase class. This time, we'll launch the UML Operation Properties dialog directly, without first going through the UML Class Properties dialog. To do this:

  1. In the Model Explorer, right-click the DataAccessBase class node and select New | Operation from the context menu. In the Name text box, enter SaveDataSet.

  2. In the Return type combo box, select the integer data type of your target programming language. For example, if you're going to implement your classes in C#, select C#::int, or if using C++, select C++:int, or if Visual Basic .NET, select VB::Integer. In this example, let's select C#::int.

  3. In the Documentation box, enter:

    Saves the passed DataSet and returns an integer specifying the number of rows updated.

  4. In the Categories pane on the left, select Method, and then uncheck the Has method checkbox, indicating the operation is abstract.

  5. Now select Parameters in the Categories pane, and then click the New button. This adds a new record to the Parameters grid. Click the Properties button to launch the UML Parameter Properties dialog.

  6. Set the parameter Name to ds, the Type to DataSet, and the Documentation to DataSet to be saved, and then click the OK button to close the dialog.

  7. Click the New button and Properties button again to add another parameter to the operation named 'databaseKey', of the type 'string', and with the description:

    Specifies a lookup key for the database in the application configuration file.

  8. When you're done, click OK to close the dialog.

Your DataAccessBase class on the class diagram should now look like the following image. Note the full signature of class operations is displayed:

If you expand the GetDataSet and SaveDataSet operation nodes of the DataAccessBase class in the Model Explorer, you'll see three parameters listed under each as shown in the following diagram. The GetDataSet and SaveDataSet parameters represent the return values of their respective operations.

Personally, I think this is a bit confusing; because return values are not truly parameters, they should be displayed with a different visual cue.

If you double-click a parameter node (or right-click the node and select Properties from the context menu), it launches the UML Parameter Properties dialog allowing you to view or edit the parameter's properties. You can easily add new parameters to an operation by right-clicking the operation node and selecting New | Parameter... from the context menu. You can also delete a parameter by right-clicking it and selecting Delete from the context menu, or by selecting the parameter and pressing the Delete key.

Creating Concrete Subclasses

Now that we have an abstract data access class, we can create a few concrete subclasses. We'll create these classes in quick succession and then show you how to make them subclasses of the DataAccessBase class. You create these classes using the UML Class Properties dialog that is launched by right-clicking the Top Package | Wrox | UMLDotNet | Data package in the Model Explorer and selecting New | Class from the context menu. Set the following name and description for each of these classes (you can accept all other default settings):

Name

Description

DataAccessSql

Accesses SQL Server data using the SQL Server .NET Data Provider classes.

DataAccessOracle

Accesses Oracle data using the Oracle .NET Data Provider classes.

DataAccessOleDb

Accesses any data for which there is a classic OLE DB provider, using the OLE DB .NET Data Provider classes.

Now we can specify that these new classes are derived from the DataAccessBase class. To do this, drag and drop each of these classes onto the class (static structure) diagram. Next, make sure the UML Static Structure stencil is open as shown in the following diagram - this stencil contains shapes you can use on your static structure diagrams. Now drag and drop a Generalization shape (a line with a single, solid arrow head) onto the diagram for each subclass. In Visio, when you want to add two or more instances of the same shape to a diagram, just drag and drop the first shape, and then press Ctrl+D for each additional shape you want to add.

For each Generalization shape, connect the arrowhead to the bottom of the DataAccessBase class, and connect the other end of the arrow to the top of a concrete subclass. When you're done, your class diagram should look like the following image.

Now we have three concrete data-access classes to use for accessing different types of back-end data. Depending on the type of data that a business object needs to access, it can instantiate one or more of these classes. These classes act as wrappers for the different .NET data provider classes, giving us some important advantages. First of all, these classes encapsulate the complexity of ADO.NET classes, by allowing us to call higher-level methods that sweat the details of ADO.NET for us behind the scenes. Since our data access classes are all derived from the DataAccessBase class, it also allows the business object to reference any concrete data-access class the same way - by treating it as if it is of the type DataAccessBase. This allows us to have generic code throughout our business object rather than SQL Server, Oracle, or OLE DB-specific code.

Each of these new concrete subclasses inherits the GetDataSet and SaveDataSet operations declared in the DataAccessBase class, and this is reflected in the Visio model. To see this, right-click the DataAccessSql class node and select Properties from the context menu to launch the UML Class Properties dialog. In the Categories pane, select Operations. We haven't added any custom operations to the DataAccessSql class, so there are no operations listed in the grid. However, at the bottom of the Operations grid is a tab selector (similar to Excel's worksheet selector) that lists all of the classes in the DataAccessSql hierarchy. If you select the DataAccessBase tab, you see the GetDataSet and SaveDataSet operations belonging to the DataAccessBase class displayed in the grid as shown in the following image:

Creating a Business Object Base Class

Now that we have a family of data access classes, we're ready to create a business object base class. One of the first things we need to decide is the .NET type to use as the base class for our business object class.

Choosing a .NET Type for Your Business Object Class

A great candidate for our business object's base class is the MarshalByRefObject type, because classes derived from this type can be accessed remotely. When creating n-tier applications, assemblies containing business objects are often placed on application servers and accessed from workstations. Using MarshalByRefObject gives our business objects this functionality.

Based on this, we need to add the MarshalByRefObject class to our Visio model:

  1. In the Model Explorer, right-click the Top Package | System package and select New | Class... from the context menu to launch the UML Class Properties dialog.

  2. In the Name text box, enter MarshalByRefObject.

  3. In the Documentation box, enter the description of this class found in the .NET Help file:

    Enables access to objects across application domain boundaries in applications that support remoting.

  4. Click OK to save changes, and close the dialog.

If you'd like, you can add all classes in the hierarchy for MarshalByRefObject as well as the DataSet class we added earlier. The MarshalByRefObject class derives directly from System.Object. You can add the Object class directly to the System package in the Visio model. The DataSet class derives from the System.ComponentModel.MarshalByValueComponent, which in turn derives from System.Object. To add this class to the model, you need to add a ComponentModel package below the System package (representing the .NET System.ComponentModel namespace), and then add a MarshalByValueComponent class to the package. After you've added these classes, your System package should look like the following image:

You can also add a new static structure diagram to the Visio model (named something like .NET Base classes) where you specify the generalization relationships between these classes. You can add this diagram to the model by right-clicking the Top Package node and selecting New | Static Structure Diagram from the context menu. This diagram should look like the following image:

Although we've added these .NET base classes to our model to show how it's done, rather than creating your Visio UML models from scratch, you should create them from a Visio model that already contains most of the common .NET base classes.

You can create such a template model yourself - in Chapter 5, where we look at reverse engineering code with Visio, we see how to create limited models of the .NET base classes.

Creating the BusinessObject Class

Now we're ready to create our business object base class.

  1. Create a new package named Business in the Visio model below the Top Package | Wrox | UMLDotNet package, and specify the following in the Documentation box:

    The Wrox.UMLDotNet.Business namespace contains business object and related classes.

  2. Add a new class named BusinessObject to the new Business package. When creating the class, accept all the default settings in the UML Class Properties dialog, and give it the description Business object base class.

  3. Drag and drop the new BusinessObject class from the Model Explorer onto the Business and Data Classes static structure diagram.

  4. Drop the MarshalByRefObject class on the diagram and specify that BusinessObject is derived from MarshalByRefObject by adding a generalization relationship between the two. If you have System.Object in the model, you can also drop this class on the diagram. If you've already specified that MarshalByRefObject is a subclass of System.Object in the .NET Base Classes diagram, then a generalization object is automatically added to the diagram when you drop System.Object onto it.

When you're finished, the static structure diagram should look like this (not including the data-access classes):

Specifying a Composition Relationship

Now we can use the class diagram to specify the association between the business object and the data-access classes. There are many associations between classes that can be modeled in the UML. One such relationship is aggregation, which is a whole-part relationship. This sort of relationship exists between our business object and data-access classes - but with a special twist. In this aggregation, there is strong ownership between the business object and data-access class - the data-access class lives and dies with the business object class. This special form of aggregation is known as composition.

To define a composition association between the business object and data-access classes, drag a Composition shape from the UML Static Structure shapes stencil, and drop it on the class diagram. Attach the diamond to the business object class and attach the other end of the composition shape to the DataAccessBase class. When you're finished, your diagram should look like the one shown in the following image (we've left out the BusinessObject class hierarchy):

In the UML, a filled-in aggregation diamond indicates composition. The default one-to-many multiplicity, indicated by the 1 (one) and asterisk (*), is exactly what we need for the BusinessObject and DataAccessBase classes.

The -End1 and -End2 text strings are placeholders for roles that can be specified for associated objects. In this context, we don't need to specify roles, so we'll remove them.

To do this, double-click the aggregation shape to launch the UML Association Properties dialog as shown in the following screenshot:

Normally, you don't need to give the association or its end points names, so we'll leave the association name set to its default value, and we'll simply remove the end-point role names. To clear the names, remove the End1 and End2 text from the records in the Association Ends grid. You can also click the Properties... button and edit each association end point individually in the UML Association End Properties dialog that provides access to additional properties as shown in the following image:

Regardless of the method you choose to remove the end points, when you click OK to save changes from the UML Association Properties dialog, the end-point names are no longer visible in the class diagram.

Adding Overloaded BusinessObject Operations

Now we're ready to specify some basic data-access operations for our business object base class. We will add GetDataSet and SaveDataSet operations to the BusinessObject class corresponding to methods of the same name on our data-access classes. When these methods are called on the business object class, the business object passes the call to an associated data-access class. Because we have a family of data-access classes, we can vary the type of data we work with by using different concrete data-access classes. For example, if we want to access data in an Oracle database, the business object passes the call to an instance of the DataAccessOracle class. If we want to access data in a SQL Server database, the business object passes the call to an instance of the DataAccessSql class.

In the business object class, we'll create a few different implementations of the GetDataSet and SaveDataSet methods that provide an opportunity to learn how overloaded methods are modeled in a UML class diagram.

Adding the GetDataSet Operations

Now we're going to add several operations to the BusinessObject class, so let's launch the UML Class Properties dialog. There are four ways to launch this dialog:

  • Double-click the BusinessObject node in the Model Explorer.

  • Right-click the Business Object node in the Model Explorer and select Properties from the context menu.

  • Double-click the BusinessObject class located on the class diagram.

  • Right-click the BusinessObject class located on the class diagram and select Properties from the context menu.

As a refresher, here are the basic steps for adding a new operation:

  1. Select Operations in the Categories pane of the UML Class Properties dialog. Click the New button, and then click the Properties... button to launch the UML Operation Properties dialog.

  2. Change operation property values and click OK to save changes.

And the steps for adding parameters to an operation:

  1. Select Parameters in the Categories pane of the UML Operation Properties dialog. Click the New button, and then click the Properties... button to launch the UML Parameter Properties dialog.

  2. Change parameter property values and click OK to save changes.

With this knowledge in hand, add three operations named GetDataSet to the BusinessObject class. These operations should all return a DataSet and their visibility should be set to protected. Also, specify the operation description, parameters, and parameter descriptions (shown in parentheses) listed in the following table:

Name

Description

Parameters

GetDataSet

Retrieves a DataSet containing a result set for the specified SQL SELECT string.

command : string (SQL command to be executed)

GetDataSet

Retrieves a DataSet containing a result set for the specified SQL SELECT string and database key.

command : string (SQL command to be executed)

databaseKey : string (Database Key)

GetDataSet

Retrieves a DataSet containing a result set for the specified SQL SELECT string and database key. Results are stored in the specified DataTable name.

command : string (SQL command to be executed)

databaseKey : string (Database Key)

tableName : string (DataTable name)

In this section, we've added three overloaded GetDataSet operations to the BusinessObject class. These operations allow us to retrieve data by passing a SQL SELECT string and optionally, a database key and table name.

Adding the SaveDataSet Operations

Now add three overloaded operations named SaveDataSet to the BusinessObject class. These operations should all return an integer and their visibility should be set to protected. Also, specify the operation description, parameters, and parameter descriptions (shown in parentheses) listed in the following table:

Name

Description

Parameters

SaveDataSet

Saves the passed DataSet.

ds : DataSet (DataSet to be saved)

SaveDataSet

Saves the passed DataSet into the database specified by the database key.

ds : DataSet (DataSet to be saved)

databaseKey : string (Database key)

SaveDataSet

Saves the passed DataSet into the database specified by the database key. Data is saved from the specified DataTable name.

ds : DataSet (DataSet to be saved)

databaseKey : string (Database key)

tableName : string (DataTable name)

When you're done, the BusinessObject class located on the class diagram should look like the following image:

Both the GetDataSet and SaveDataSet operations have overloads allowing you to pass a database key and a table name. Typically, in a .NET application, we store database information in an XML configuration file, which allows us to retrieve database information such as connection strings. If we have more than one database in our application, the database key allows us to specify which database we want to retrieve information for from the configuration file. The table name parameter specifies the name of the DataTable you want to work with.

The sharp sign (#) indicates these operations are protected. The reason we have protected these methods is so consumers of our business objects cannot call them directly. For example, if we make the GetDataSet operations public, it provides an opening for consumers of the object to pass any SQL SELECT command conceivable and return secure data stored in the database. Later we'll add higher-level public methods to the business object passing predetermined SQL SELECT strings to these operations. In a more robust data-access model, we would also provide methods that allow us to call stored procedures. However, for this simple model our existing methods will suffice.

Adding Attributes to the BusinessObject Class

Next, we are going to add two attributes to the BusinessObject class - DatabaseKey and TableName. These attributes hold default values in cases where the database key and table name are not specified in calls to the GetDataSet and SaveDataSet operations. For example, if a call is made to the overload of GetDataSet that has only a single command parameter, default database key and table name values can be retrieved from the DatabaseKey and TableName attributes.

Don't let the term "attribute" throw you. UML attributes are not the same as .NET attributes. Attributes in a UML class map to fields in a C# class and member variables in a Visual Basic .NET class, as shown in the table earlier in this chapter.

You add new attributes to the BusinessObject class using the UML Attribute Properties dialog. If you are adding only one attribute, you can launch this dialog by right-clicking the BusinessObject class in the Model Explorer and selecting New | Attribute from the shortcut menu. However, we want to add two properties, so we'll launch this dialog from the UML Class Properties dialog.

To do this, double-click the BusinessObject class node in the Model Explorer. In the Categories pane of the UML Class Properties dialog, select Attributes. Click the New button, which adds a new record to the Attributes grid, and then click the Properties button to launch the UML Attribute Properties dialog shown in the following image.

The following table describes some of the more important entry fields in the UML Attribute Properties dialog and how they relate to .NET languages such as C# and Visual Basic .NET:

Entry Field

Description

Name

Attribute name.

Type

The attribute type.

Visibility

There are three choices - private, protected, and public. Again, there are two visibility options missing - internal and protected internal in C# and the equivalent Friend and Protected Friend in VB .NET. As discussed earlier, there are no equivalent visibility options in Visio.

Multiplicity

Specifies the number of data values the property can store. Typically, this is one (1), but if you have a property that holds more than one value (such as an indexer), you can choose a different option.

InitialValue

The initial value of the attribute.

Changeable

The Changeable combo box contains three options - none, frozen, and addOnly. The default selection is none, which means there are no restrictions on changing the value. The frozen option specifies the value of the attribute cannot be changed. This is equivalent to a read-only property in C# and Visual Basic .NET. The addOnly option applies to attributes with a multiplicity other than one (1). It specifies values can be added to the attribute, but cannot later be removed or changed.

OwnerScope

The instance option is equivalent to instance properties in C# and VB .NET. The classifier option is the same as static in C# and Shared in VB .NET, and these actually do map to static and Shared when generating code from the model.

  1. In the Name textbox, enter DatabaseKey.

  2. In the Type combo box, select one of the string data types.

  3. Set the Visibility to protected because this attribute does not need to be accessed by clients using the BusinessObject class, but should be accessible by classes derived from it.

  4. In the Documentation box, enter the following:

    The default database key used to look up database information from the application configuration file.

  5. Click OK to save changes.

Now follow the same steps outlined above and add a second protected string attribute named TableName, with the following documentation:

  • The default DataTable name of DataSets manipulated by the business object.

When you're done, click OK to save changes, and then click OK to close the UML Class Properties dialog. The BusinessObject class located on the class diagram should now look like the following image:

Team FLY

Категории