Building the Territory Classes


With that said, let's start building the Territory objects. As before, you need to create your four stored procedures for performing all of your operations in the database. Execute the SQL in Listing 7-1 against the Northwind database.

Listing 7-1: The Territory Stored Procedures

start example
 USE northwind go CREATE PROCEDURE usp_territory_delete @id nvarchar(20) AS DELETE FROM Territories WHERE TerritoryID = @id go CREATE PROCEDURE usp_territory_getall AS SELECT  * FROM    Territories go CREATE PROCEDURE usp_territory_getone @id nvarchar(20) AS SELECT  * FROM  Territories WHERE TerritoryID = @id go CREATE PROCEDURE usp_territory_save @id nvarchar(20), @territory nchar(50), @region_id int, @new bit AS IF @new = 1    INSERT INTO Territories (TerritoryID, TerritoryDescription, RegionID)       VALUES (@id, @territory, @region_id) ELSE    UPDATE Territories    SET TerritoryDescription = @territory,       RegionID = @region_id    WHERE TerritoryID = @id 
end example

You will notice that the Territories table does not have a numeric, autogenerated identifier as a key. Although I do not agree with this approach—because it leaves the burden of creating a primary key value on the user and because it can be any value the user wants—you will leave it as is because that is the way the database was constructed. Once you have created all of the stored procedures, it is time to create the structure and the interface. Then you will create the data-centric business object.

Caution

Having a user-created key on a table can cause an incredible amount of problems in code. You will see why this is a problem later in the "Creating the frmTerritoryList Form" section. I have said it before and I will say it again: All keys in a database should be surrogate keys because it makes everyone's life easier.

Creating the Territory Structure

To create the structure and the interface, edit the Structures.vb code module and add the following structure:

 <Serializable()> Public Structure structTerritory     Public TerritoryID As String     Public TerritoryDescription As String     Public RegionID As Integer     Public IsNew as Boolean End Structure 

Notice that you have added a property that does not exist in the database: the IsNew property. In the case of the Region class, it was easy to determine if you were going to perform a save or an update because you could check to see if the ID was zero. Because the TerritoryID is a string, you cannot check it. You also cannot check to see if it is an empty length string because the user needs to set it if it is new—that is one of the rules your object will implement. The only way to have the save stored procedure perform the right operation is to set this flag.

Creating the Territory Interface

Next you need to create the interface for this object. Add the following interface to the Interfaces.vb code module:

 Public Interface ITerritory     Function LoadProxy() As DataSet     Function LoadRecord(ByVal strID As String) As structTerritory     Function Save(ByVal sTerritory As structTerritory) _     As BusinessErrors     Sub Delete(ByVal strID As String)     Function GetBusinessRules() As BusinessErrors End Interface 

Notice that this interface is almost identical to the IRegion interface except for the LoadRecord, Delete, and Save signatures. The difference between these three signatures is the structure that is either passed or returned and the data type of the integer. Also, in the case of the Save method, you do not need to return the ID because that is set by the user. In the next chapter, when you work on reducing your redundant code, this will come into play.

Creating the TerritoryDC Object

Because this object is materially like the Region object, you will see the code in Listing 7-2 and examine the differences afterward. Add a new class to the NorthwindDC project and call it TerritoryDC. Next, add the code from Listing 7-2.

Listing 7-2: The TerritoryDC Object

start example
 Option Strict On Option Explicit On Imports NorthwindTraders.NorthwindShared.Interfaces Imports NorthwindTraders.NorthwindShared.Structures Imports NorthwindTraders.NorthwindShared.Errors Imports System.Configuration Imports System.Data.SqlClient Public Class TerritoryDC     Inherits MarshalByRefObject     Implements ITerritory     Private mobjBusErr As BusinessErrors #Region " Private Attributes"     Private mstrTerritoryID As String     Private mstrTerritoryDescription As String     Private mintRegionID As Integer #End Region #Region " Public Attributes"     Public Property TerritoryID() As String         Get             Return mstrTerritoryID         End Get         Set(ByVal Value As String)             Try                 If Value Is Nothing Then                     Throw New ArgumentNullException("Territory ID")                 End If                 If Value.Length = 0 Then                     Throw New ZeroLengthException()                 End If                 If Value.Length > 20 Then                     Throw New MaximumLengthException(20)                 End If                 mstrTerritoryID = Value             Catch exc As Exception                 mobjBusErr.Add("Territory ID", exc.Message)             End Try         End Set     End Property     Public Property TerritoryDescription() As String         Get             Return mstrTerritoryDescription         End Get         Set(ByVal Value As String)             Try                 If Value Is Nothing Then                     Throw New ArgumentNullException("Territory Description")                 End If                 If Value.Length = 0 Then                     Throw New ZeroLengthException()                 End If                 If Value.Length > 50 Then                     Throw New MaximumLengthException(50)                 End If                 mstrTerritoryDescription = Value             Catch exc As Exception                 mobjBusErr.Add("Territory Description", exc.Message)             End Try         End Set     End Property     Public Property RegionID() As Integer         Get             Return mintRegionID         End Get         Set(ByVal Value As Integer)             Try                 If Value = 0 Then                     Throw New ArgumentNullException("Region")                 End If                 mintRegionID = Value             Catch exc As Exception                 mobjBusErr.Add("Region", exc.Message)             End Try         End Set     End Property #End Region     Public Function LoadProxy() As DataSet Implements ITerritory.LoadProxy         Dim strCN As String = ConfigurationSettings.AppSettings("Northwind_DSN")         Dim cn As New SqlConnection(strCN)         Dim cmd As New SqlCommand()         Dim da As New SqlDataAdapter(cmd)         Dim ds As New DataSet()         cn.Open()         With cmd             .Connection = cn             .CommandType = CommandType.StoredProcedure             .CommandText = "usp_territory_getall"         End With         da.Fill(ds)         cmd = Nothing         cn.Close()         Return ds     End Function     Public Function LoadRecord(ByVal strID As String) As _     structTerritory Implements ITerritory.LoadRecord         Dim strCN As String = ConfigurationSettings.AppSettings("Northwind_DSN")         Dim cn As New SqlConnection(strCN)         Dim cmd As New SqlCommand()         Dim da As New SqlDataAdapter(cmd)         Dim ds As New DataSet()         Dim sTerritory As structTerritory         cn.Open()         With cmd             .Connection = cn             .CommandType = CommandType.StoredProcedure             .CommandText = "usp_territory_getone"             .Parameters.Add("@id", strID)         End With         da.Fill(ds)         cmd = Nothing         cn.Close()         With ds.Tables(0).Rows(0)             sTerritory.TerritoryID = Convert.ToString(.Item("TerritoryID"))             sTerritory.TerritoryDescription = _             Convert.ToString(.Item("TerritoryDescription"))             sTerritory.RegionID = Convert.ToInt32(.Item("RegionID"))         End With         ds = Nothing         Return sTerritory     End Function     Public Sub Delete(ByVal strID As String) Implements ITerritory.Delete         Dim strCN As String = ConfigurationSettings.AppSettings("Northwind_DSN")         Dim cn As New SqlConnection(strCN)         Dim cmd As New SqlCommand()         cn.Open()         With cmd             .Connection = cn             .CommandType = CommandType.StoredProcedure             .CommandText = "usp_territory_delete"             .Parameters.Add("@id", strID)             .ExecuteNonQuery()         End With         cmd = Nothing         cn.Close()     End Sub     Public Function Save(ByVal sTerritory As structTerritory) _     As BusinessErrors Implements ITerritory.Save         Dim strCN As String = ConfigurationSettings.AppSettings("Northwind_DSN")         Dim cn As New SqlConnection(strCN)         Dim cmd As New SqlCommand()         Dim intNew As Integer         mobjBusErr = New BusinessErrors()         With sTerritory             Me.TerritoryID = .TerritoryID             Me.TerritoryDescription = .TerritoryDescription             Me.RegionID = .RegionID         End With         If mobjBusErr.Count = 0 Then            If sTerritory.IsNew Then                 intNew = 1            Else                 intNew = 0            End If             cn.Open()             With cmd                 .Connection = cn                 .CommandType = CommandType.StoredProcedure                 .CommandText = "usp_territory_save"                 .Parameters.Add("@id", mstrTerritoryID)                 .Parameters.Add("@territory", mstrTerritoryDescription)                 .Parameters.Add("@region_id", mintRegionID)                 .Parameters.Add("@new", intNew)                 .ExecuteNonQuery()             End With             cmd = Nothing             cn.Close()         End If         Return mobjBusErr     End Function     Public Function GetBusinessRules() As BusinessErrors _     Implements ITerritory.GetBusinessRules         Dim objBusRules As New BusinessErrors()         With objBusRules             .Add("Territory ID", "The value cannot be null.")             .Add("Territory ID", "The value cannot be more than 20 " _             & "characters in length.")             .Add("Territory Description", "The value cannot be null.")             .Add("Territory Description", "The value cannot be more " _             & "than 50 characters in length.")             .Add("Region", "The value cannot be null.")         End With         Return objBusRules     End Function End Class 
end example

That is a lot of code, but after this you will not have to type in that much code at one time for the rest of the project! The biggest change in Listing 7-2 is the Save method. Because you already know what the ID of the new record is, you do not have to pass it back. Aside from this, this code is almost identical to the code for the RegionDC class.

Building the User-Centric Territory Classes

The next thing you need to do is to build the user-centric Territory classes. This is where you have to implement the choice you made concerning the object relationships. The user-centric business objects are coupled with the user interface. It is not a tight coupling, but many times you make choices to support your own user interfaces. If you make this object loosely coupled and another application wants to use your user-centric business objects, they may not realize they need to instantiate the Region objects on their own and therefore your Territory object will not be complete. So, you will create an association between this class and the Region class because it is a simple starting point. Because this class is an aggregate of another class, you will see some slightly different behavior in your class.

Creating the Territory Class

Add a new class module to the NorthwindUC project called Territory.vb. As before, add the following code to the top of the Territory.vb code module:

 Option Strict On Option Explicit On Imports NorthwindTraders.NorthwindShared.Structures Imports NorthwindTraders.NorthwindShared.Interfaces Imports NorthwindTraders.NorthwindShared.Errors Imports NorthwindTraders.NorthwindShared 

Now, let's start adding code to your Territory class.

 Private WithEvents mobjRules As BrokenRules Private mblnDirty As Boolean = False Public Loading As Boolean Private Const LISTENER As String = "TerritoryDC.rem" Private msTerritory As structTerritory Private mblnNew As Boolean Public Event BrokenRule(ByVal IsBroken As Boolean) Public Event ObjectChanged(ByVal sender As Object, _ ByVal e As ChangedEventArgs) 

A new variable in the preceding code (if you compare this to the Region class) is the mblnNew variable. This is the flag you will set to indicate whether the object is new. Besides that change, the code is the same so far. This is the reason for using the generic object to pass back your object in the ObjectChanged event; you can copy and paste it everywhere without making a single change to the signature.

Note

In the next chapter you will see how you can reduce a large amount of redundancy within the two classes you have created so far (the Region and Territory classes). It allows you to create a standard base class that can be used by everything and that ensures you have implemented the BrokenRules class and several other things you have determined as mandatory for your classes.

Add the following private member attributes to the Territory class:

 #Region " Private Attributes"     Private mstrTerritoryID As String = ""     Private mstrTerritoryDescription As String = ""     Private mobjRegion As New Region #End Region 

Notice that instead of a RegionID variable you have an instance of the Region object. This is the actual object dependency and it forces you to load and refresh your object in a slightly different way. Add the public attributes of your class as shown in Listing 7-3.

Listing 7-3: Public Attributes of the Territory Class

start example
 #Region " Public Attributes"     Public Property TerritoryID() As String         Get             Return mstrTerritoryID         End Get         Set(ByVal Value As String)             Try                 If Value Is Nothing Then                     Throw New ArgumentNullException("Territory ID")                 End If                 If Value.Length = 0 Then                     Throw New ZeroLengthException()                Else                      If Value.Length > 20 Then                          Throw New MaximumLengthException(20)                      End If                 End If                 If mstrTerritoryID <> Value Then                     mstrTerritoryID = Value                     If Not Loading Then                         mobjRules.BrokenRule("Territory ID", False)                         mblnDirty = True                     End If                 End If             Catch exc As Exception                 mobjRules.BrokenRule("Territory ID", True)                 mstrTerritoryID = Value                 mblnDirty = True                 Throw exc             End Try         End Set     End Property     Public Property TerritoryDescription() As String         Get             Return mstrTerritoryDescription         End Get         Set(ByVal Value As String)             Try                 If Value Is Nothing Then                     Throw New ArgumentNullException("Territory Description")                 End If                 If Value.Length = 0 Then                     Throw New ZeroLengthException()                Else                      If Value.Length > 50 Then                          Throw New MaximumLengthException(50)                      End If                 End If                 If mstrTerritoryDescription <> Value Then                     mstrTerritoryDescription = Value                     If Not Loading Then                         mobjRules.BrokenRule("Territory Description", False)                         mblnDirty = True                     End If                 End If             Catch exc As Exception                 mobjRules.BrokenRule("Territory Description", True)                 mstrTerritoryDescription = Value                 mblnDirty = True                 Throw exc             End Try         End Set     End Property     Public Property Region() As Region         Get             Return mobjRegion         End Get         Set(ByVal Value As Region)             Try                 If Value Is Nothing Then                     Throw New ArgumentNullException("Region")                 End If                 If Not Value Is mobjRegion Then                     mobjRegion = Value                     If Not Loading Then                         mobjRules.BrokenRule("Region", False)                         mblnDirty = True                     End If                 End If             Catch exc As Exception                 mobjRules.BrokenRule("Region", True)                 mobjRegion = Value                 mblnDirty = True                 Throw exc             End Try         End Set     End Property #End Region 
end example

This follows the exact same pattern as the one you used when you created your Region object, except for the inclusion of a Region object here. Notice that you are using a property for the Region object that accepts a Region object ByVal. In actual practice, however, this is a reference type and so it is passed by reference. You will see this once you are done creating this object and you test it. It should be noted that the Region objects associated using this property will not be destroyed if you destroy this object. You should only do this type of setup for object relationships that consist of dependencies, rather than true aggregations. In a true aggregation, the supplier object (Region) would be created by the client object (Territory).

Tip

One of the great powers of this type of setup is the consistency of the code. You do not have to do anything radically different from one class to the other, which helps make maintenance incredibly easy. It also makes reading the code fairly easy because if you understand it in one place, you understand it in all places.

Next, add the IsDirty and IsValid properties (or copy them from the Region object) as shown in Listing 7-4.

Listing 7-4: The IsDirty and IsValid Methods

start example
 Public ReadOnly Property IsDirty() As Boolean      Get           Return mblnDirty      End Get End Property Public ReadOnly Property IsValid() As Boolean      Get           If mobjRules.Count > 0 Then                Return False           Else                Return True           End If      End Get End Property 
end example

Although the IsDirty and IsValid classes remain the same, the constructors will be slightly different. Add the constructors as shown in Listing 7-5.

Listing 7-5: The Territory Class Constructors

start example
 Public Sub New()      mblnNew = True      mobjRules = New BrokenRules()      mobjRules.BrokenRule("Territory ID", True)      mobjRules.BrokenRule("Territory Description", True)      mobjRules.BrokenRule("Region", True) End Sub Public Sub New(ByVal strID As String)      mblnNew = False      mobjRules = New BrokenRules()      mstrTerritoryID = strID End Sub 
end example

In the first constructor, you set the New flag to true, which is the indicator to the stored procedure to execute the correct block of code when you go to save your object. Also, in this method, you set all of the properties as broken because they are all required and none of them have defaults. The second constructor is similar to the Region constructor except that you set the New flag to false because any changes will indicate an update to the object, not a new object. Also, notice you do not break any rules. The reason for this is that it is assumed you will load your object with valid values, so this saves a bunch of unneeded processing. Also, you have a backup system here because if you assign properties that are invalid, the rules will be broken for you.

Add the ToString function as follows:

 Public Overrides Function ToString() As String      Return mstrTerritoryDescription End Function 

The ToString function remains the same, except that you now return the Territory Description. Next, add the LoadRecord routine. You will recall that this routine is used either to fully load the record if it is only partially loaded or to refresh the object from the database so that when a user goes to edit the record, they are editing the latest record. Add the LoadRecord method as shown in Listing 7-6.

Listing 7-6: The LoadRecord Method

start example
 Public Sub LoadRecord()      Dim objITerritory As ITerritory      Dim sTerritory As structTerritory      Dim objRegionMgr As RegionMgr      objITerritory = CType(Activator.GetObject(GetType(ITerritory), _      AppConstants.REMOTEOBJECTS & LISTENER), ITerritory)      sTerritory = objITerritory.LoadRecord(mstrTerritoryID)      objITerritory = Nothing      With sTerritory           Me.mstrTerritoryID = .TerritoryID           Me.mstrTerritoryDescription = .TerritoryDescription.Trim      End With      objRegionMgr = objRegionMgr.GetInstance      mobjRegion = objRegionMgr.Item(sTerritory.RegionID)      sTerritory = Nothing End Sub 
end example

This method is similar to the Region class's LoadRecord method except for the addition of the RegionMgr object. The advantage of creating the RegionMgr object as a Singleton object is apparent. If you have not loaded the RegionMgr object at this point, it is loaded. Then you retrieve an object from the Manager. If the object is loaded, then you do not have to go to the database to get the object. See the sidebar "Singleton Object Dangers" for additional information.

start sidebar
Singleton Object Dangers

You need to be aware of a couple of things with Singleton objects and aggregation. What if there was a new Region added to the database and the current Territory you were working with was updated so that it was assigned to this new Region? And what if the old Region had been deleted? This scenario is not likely in this situation, but let's look at what you would have to do to take care of this problem.

First, when the updated Territory information is received from the database, you would need to check the value of the RegionID against the value of the RegionID in the Region object. If they matched, then there is no problem and you can just call the Load method on the aggregate object. If they did not match, well, this is where it gets sticky. First, you would need to get a reference to the RegionMgr object and check to see if the object was in the collection. If it is in the collection, then there is no problem. You just assign that Region object to your Territory object and call the Load method on it. If it was not there, you would need to call the Refresh method on the RegionMgr object and then get a reference to the new Region and assign it to your Territory object.

Although all of this works great, there is the small problem in that your objects are no longer loosely coupled. Instead they are tightly bound and one object needs to know everything about the other object. Of course, this is precisely what an aggregate is, but remember that it is your choice as to whether you aggregate your objects. There are no hard and fast rules, and this type of scenario demands that you examine your object relationships carefully before you begin coding.

end sidebar

Next is the Delete method, which again is the same except for the object that is being deleted. Add the code for this method as shown in Listing 7-7.

Listing 7-7: The Territory Delete Method

start example
 Public Sub Delete()      Dim objITerritory As ITerritory      objITerritory = CType(Activator.GetObject(GetType(ITerritory), _      AppConstants.REMOTEOBJECTS & LISTENER), ITerritory)      objITerritory.Delete(mstrTerritoryID)      objITerritory = Nothing End Sub 
end example

Next, add the Save method shown in Listing 7-8 to the Territory class.

Listing 7-8: The Territory Save Method

start example
 Public Sub Save()      If mobjRules.Count = 0 Then           If mblnDirty = True Then                Dim objITerritory As ITerritory                Dim sTerritory As structTerritory                With sTerritory                     .TerritoryID = mstrTerritoryID                     .TerritoryDescription = mstrTerritoryDescription                     .RegionID = mobjRegion.RegionID                     .IsNew = mblnNew                End With                objITerritory = _                CType(Activator.GetObject(GetType(ITerritory), _                AppConstants.REMOTEOBJECTS & LISTENER), ITerritory)                objITerritory.Save(sTerritory)                objITerritory = Nothing                If mblnNew Then                     mblnNew = False                     RaiseEvent ObjectChanged(Me, New _                     ChangedEventArgs(ChangedEventArgs.eChange.Added))                Else                     RaiseEvent ObjectChanged(Me, New _                     ChangedEventArgs(ChangedEventArgs.eChange.Updated))                End If           End If      End If End Sub 
end example

The only difference between this method and the Save method in the Region class is that you check the value of the mblnNew flag instead of checking to see if the ID is equal to 0. Note also that you have to manually set the New flag to false once the object has been saved. The last two methods, GetBusinessRules and BrokenRules, are the same (shown in Listing 7-9). Add these methods to the Territory class.

Listing 7-9: The GetBusinessRules and BrokenRules Methods

start example
 Public Function GetBusinessRules() As BusinessErrors      Dim objITerritory As ITerritory      Dim objBusRules As BusinessErrors      objITerritory = CType(Activator.GetObject(GetType(ITerritory), _      AppConstants.REMOTEOBJECTS & LISTENER), ITerritory)      objBusRules = objITerritory.GetBusinessRules      objITerritory = Nothing      Return objBusRules End Function Private Sub mobjRules_RuleBroken(ByVal IsBroken As Boolean) _ Handles mobjRules.RuleBroken      RaiseEvent BrokenRule(IsBroken) End Sub 
end example

Adding the TerritoryMgr Class

Next you need to add the TerritoryMgr class. This class is also a Manager class, but it is not a Singleton. The reason for this is that you do want to know what territories the employee is a part of, so every employee you load needs to have its own collection of territories. Listing 7-10 shows the code for the TerritoryMgr class. Add this code to the Territory.vb code module.

Listing 7-10: The TerritoryMgr Class

start example
 Public Class TerritoryMgr      Inherits System.Collections.DictionaryBase      Public Sub New           Load      End Sub      Public Sub Add(ByVal obj As Territory)           dictionary.Add(obj.TerritoryID, obj)      End Sub      Public Function Item(ByVal Key As Object) As Territory           Return CType(dictionary.Item(Key), Territory)      End Function      Public Sub Remove(ByVal Key As Object)           dictionary.Remove(Key)      End Sub      Private Sub Load()           Dim objITerritory As ITerritory           Dim dRow As DataRow           Dim ds As DataSet           Dim objRegionMgr As RegionMgr           objITerritory = CType(Activator.GetObject(GetType(ITerritory), _           AppConstants.REMOTEOBJECTS & "TerritoryDC.rem"), ITerritory)           ds = objITerritory.LoadProxy()           objITerritory = Nothing           objRegionMgr = objRegionMgr.GetInstance           For Each dRow In ds.Tables(0).Rows                Dim objTerritory As New _                Territory(Convert.ToString(dRow.Item("TerritoryID")))                With objTerritory                     .Loading = True                     .TerritoryDescription = _                     Convert.ToString(dRow.Item("TerritoryDescription")).Trim                     .Region = _                     objRegionMgr.Item(Convert.ToInt32(dRow.Item("RegionID")))                     .Loading = False                End With                Me.Add(objTerritory)           Next           ds = Nothing      End Sub End Class 
end example

The differences to note in the Load method revolve around the RegionMgr object. As with the Load method in the Territory class, you need to get a reference to the RegionMgr object. Remember that the RegionMgr object is a shared object, so if the Regions have been loaded prior to this, the reference is established very quickly; otherwise the information must be retrieved from the database. Then, as you load the Territory objects, you assign a Region object based on the key, which in this case, is the RegionID that you pulled back and you returned to your Territory object. Because you are assigning the Region objects to your Territory objects in this way, you do not need to instantiate a new Region object for every Territory object that you instantiate. This makes the load process fairly quick.

Now that you have added the code, you need to make another entry in the web.config file. Add the following tag so that you can access the TerritoryDC object:

 <wellknown mode="Singleton"      type="NorthwindTraders.NorthwindDC.TerritoryDC, NorthwindDC"      objectUri="TerritoryDC.rem"/> 




Building Client/Server Applications with VB. NET(c) An Example-Driven Approach
Building Client/Server Applications Under VB .NET: An Example-Driven Approach
ISBN: 1590590708
EAN: 2147483647
Year: 2005
Pages: 148
Authors: Jeff Levinson

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