Returning a Strongly Typed Collection

The difficulty in building good object-oriented systems based on relational databases is the orthogonality between a relational database and an object-oriented structure. Databases are generally made of tables with rows and columns, and relationships are inferred from key columns . The closest analogy between a single object and a database is the row. The problem is that databases tend toward normalization, resulting in many pieces of what might comprise a single object existing in more than one table. The bending and contorting between rows of normalized data to objects, back and forth, is what makes it difficult to build object-oriented systems on top of relational databases.

On the one hand, you have the lightning speed of multi-row updates, deletions, insertions, and selections that are the hallmark of a relational database; on the other hand, how do you reconcile this with behaviors? Where is there room for a behavior in a DataTable or DataRow ? A hybrid has appeared recently. This hybrid treats a DataSet as the base class for an object. The difficulty here is that you may have multiple rows of data and there is no concept of a single object; rather, the rows represent a collection of data with no closely related behaviors. The behaviors exist in the DataSet , the collection. (Attempts to solve this dilemma have led to object-oriented databases, like POET, but these databases don't seem to be catching on.)

There are many ways to build software, of course. You can compose the system from the perspective of the database, the graphical user interface, or a centralized solution captured in objects. The latter more consistently leads to reliable software but is not always easiest to implement. Perhaps until the dichotomy between relational databases and object-oriented systems is solved the best thing to do is assess the way you elect to build a particular application based on the complexity. (Very complex systems strongly lend themselves to object-oriented practices, even if the simplicity of persisting data to a relational database suffers.) While you are deciding what to do, let's look at another tool to add to your solution arsenal: returning serialized typed collections from XML Web Services.

Reviewing Strongly Typed Collections

Recall (from Chapter 5) that we can define typed collections by inheriting from the System.Collections.ReadOnlyCollectionBase class. (We can also define a writable typed collection by inheriting from the System.Collections.CollectionBase class.). A typed collection is a special class devised to collect a specific kind of object. Unlike an array, collections are capable of dynamically growing to accommodate more elements. Typed collections contain a known type. By combining the ease of use of an array of types with the dynamic growth capabilities of an array list, you end up with a typed collection. One additional benefit is that typed collections can be serialized. This makes them ideal for persisting as XML in any form, including returning them from XML Web Services.

Defining a typed collection is straightforward. Define a class that inherits from ReadOnlyCollectionBase or CollectionBase and implement a couple of methods and a default indexer. The indexer allows you to treat a typed collection like an array; the underlying collection and subsequent typecast allow you to treat the typed collection as a dynamically growing array of a specific type. Listing 14.9 demonstrates the rudiments of a typed collection of CommissionsData objects.

Listing 14.9 Implementing a Strongly Typed Collection with a Twist
 1:  Imports System.Collections 2:  Imports CommissionsData.CommissionsData 3: 4: 5:  Public Class Commission 6:    Inherits CommissionsData.CommissionsData 7: 8:    Public Overrides Function ToString() As String 9:      Const mask As String = "Record Type: {0}{1}" 10:     Return String.Format(mask, SystemCode, RecordType) 11:   End Function 12: 13: End Class 14: 15: Public Class CommissionsDataList 16:   Inherits CollectionBase 17: 18:   Default Public Property Item( _ 19:     ByVal Index As Integer) As Commission 20:   Get 21:     Return CType(List(Index), Commission) 22:   End Get 23:   Set(ByVal Value As Commission) 24:     List(Index) = Value 25:   End Set 26:   End Property 27: 28:   Public Function Add( _ 29:     ByVal Value As Commission) As Integer 30: 31:     Return List.Add(Value) 32: 33:   End Function 34: 35: End Class 

Two elements appear in Listing 14.9. Lines 15 through 35 implement the typed collection. I will return to that in a minute, preferring to proceed in a more orderly fashion from the beginning of the listing.

Line 1 imports the System.Collections namespace. The CollectionBase class I am inheriting from comes from that namespace. Line 2 imports the collected type. I generally don't name classes the same name as my namespace since it lends to confusion; however, in this instance I elected to leave it since it opens the door for another discussion, demonstrated by the Commission class beginning in line 5.

Line 5 introduces a new class. Instead of modifying an existing class that I know other code depends on (see the earlier discussions using CommissionsData ), I took an intermediate step and introduced a new class that inherits from CommissionsData . The new class (lines 5 through 13) has a better name, Commission , and new behavior. The Commission class overrides the ToString method, providing clients with an alternate behavior. There is one additional modest benefit: if my client doesn't refer to behaviors defined in CommissionsData directly ”recall that it is defined in a different assembly than the one in Listing 14.9 ”my client will not have to reference both assemblies. However, if, for instance I refer to CommissionsData.RecordType directly, I will need to reference the CommissionsData.dll too. These are pragmatic matters that you should be aware of since they will aid in your mastery of Visual Basic .NET.

As mentioned, lines 15 through 35 implement the typed collection. It is easy to implement, with just two basic members , but it is deceptively powerful. I implemented an Add method (lines 28 through 33) to allow me to add Commission objects to the collection, and I implemented a default indexer (lines 18 through 26). The default indexer allows me to treat the typed collection, syntactically, as an array. Listing 14.10 demonstrates code that shows you the typed collection's Add method and the array- and collection-like capabilities.

Listing 14.10 Using Typed Collections Like Smart, Dynamically Sizing Arrays
 1:  Imports TypedCollectionDemo 2: 3:  Module Module1 4: 5:    Sub Main() 6: 7:      Dim List As CommissionsDataList = New CommissionsDataList() 8: 9:      ' Adds a specific type, supported by the Add method 10:     List.Add(New Commission()) 11:     List.Add(New Commission()) 12: 13:     ' Uses IEnumerable implemented by CollectionBase 14:     Dim Item As Commission 15:     For Each Item In List 16:       Console.WriteLine(Item.ToString()) 17:     Next 18: 19:     ' Treats CommissionsDataList like an array, 20:     ' supported by the indexer 21:     Dim I As Integer 22:     For I = 0 To List.Count - 1 23:       Console.WriteLine(List(I).ToString()) 24:     Next 25: 26:     Console.ReadLine() 27: 28:   End Sub 29: 30: End Module 

Lines 10 and 11 demonstrate the Add method in action. Notice that we are passing a specific, homogeneous type to the Add method. Lines 14 through 17 demonstrate using the IEnumerable interface inherited from CollectionBase . Interestingly, if you download a copy of Rotor (the source code for the CLR) from http://www.microsoft.com, you will see that IEnumerable lies at the heart of serialization. Lines 21 through 24 show the same typed collection being indexed as if it were an array. If you step through this code, you will see the default indexer property getter being invoked in line 23.

That's all there is to implementing a basic typed collection. You will want to exercise a modicum of care when returning a typed collection from a Web Service because the Web Service serialization mechanisms seem to be a bit more demanding. Read on.

Serializing a Strongly Typed Collection

XML is a portable standard for transmitting data, especially data that is describing an object and its state. If you want to save the state of an object to XML, you can use code very similar to that shown in Listing 14.7. All you need to serialize a typed collection to an XML stream is an XmlSerializer object and a stream. Listing 14.11 demonstrates serializing a typed collection using a FileStream object. The output target is a text file containing XML.

Listing 14.11 Serializing a Typed Collection to an XML Stream
 Dim Serializer As XmlSerializer = _   New XmlSerializer(List.GetType()) Dim Stream As FileStream = _   New FileStream("..\list.xml", FileMode.CreateNew) Serializer.Serialize(Stream, List) Stream.Close() 

The serializer accepts a Type object of the types we want to serialize. Then the whole list is serialized to the created FileStream object. The stream represents a file in this instance. At the end of the code, we need to close the FileStream object.

We can serialize typed collections (and other things) to XML because of the use of Reflection, the SerializableAttribute , and the IEnumerable interface. Both ReadOnlyCollectionBase and CollectionBase are adorned with the SerializableAttribute and implement the IEnumerable interface. The SerializableAttribute indicates that instances of a class can be serialized. Implementing IEnumerable means that the contractee will implement a method, GetEnumerator . GetEnumerator returns an object that implements IEnumerator , allowing all the elements in the collection to be enumerated.

All the public fields properties of a class are serialized if that class is marked with the SerializableAttribute unless you implement the ISerializable interface. When you want to take control of the serialization process ”perhaps you want to serialize a conceptual value ”you can implement the ISerializable interface. ISerializable defines an explicit method, GetObjectData , and an implied constructor for deserialization. Listing 14.12 demonstrates a revision to the Commission class that shows how to implement ISerializable . (It is worth noting that some forms of serialization use the ISerializable methods, but XmlSerializer does not.)

Listing 14.12 Implementing the ISerializable Interface
 Public Class Commission   Inherits CommissionsData.CommissionsData   Implements ISerializable   Private FTransmittedDate As DateTime   Public ReadOnly Property TransmittedDate() As DateTime   Get     Return FTransmittedDate   End Get   End Property   Public Sub New()   End Sub   Protected Sub New(ByVal Info As SerializationInfo, _     ByVal context As StreamingContext)     SystemCode = Info.GetString("SystemCode")     RecordType = Info.GetString("RecordType")     ClearingSettlingFirmNumber = _       Info.GetDateTime("ClearingSettlingFirmNumber")     FundProcessingDate = Info.GetDateTime("FundProcessingDate")     CommissionType = Info.GetString("CommissionType")     DebitCreditIndicator = Info.GetString("DebitCreditIndicator")     DebitReasonCode = Info.GetString("DebitReasonCode")     SettlementIndicator = Info.GetString("SettlementIndicator")     RecordDate = Info.GetDateTime("RecordDate")     FTransmittedDate = Info.GetDateTime("TransmittedDate")   End Sub   Public Sub GetObjectData(ByVal Info As SerializationInfo, _     ByVal context As StreamingContext) _     Implements ISerializable.GetObjectData     Info.AddValue("SystemCode", SystemCode)     Info.AddValue("RecordType", RecordType)     Info.AddValue("ClearingSettlingFirmNumber", _       ClearingSettlingFirmNumber)     Info.AddValue("FundProcessingDate", FundProcessingDate)     Info.AddValue("CommissionType", CommissionType)     Info.AddValue("DebitCreditIndicator", DebitCreditIndicator)     Info.AddValue("DebitReasonCode", DebitReasonCode)     Info.AddValue("SettlementIndicator", SettlementIndicator)     Info.AddValue("RecordDate", RecordDate)     If (FTransmittedDate = DateTime.MinValue) Then       FTransmittedDate = DateTime.Now     End If     Info.AddValue("TransmittedDate", FTransmittedDate)   End Sub   Public Overrides Function ToString() As String     Const mask As String = "Record Type: {0}{1}"     Return String.Format(mask, SystemCode, RecordType)   End Function End Class 

The field playing the role of the conceptual business value is TransmittedDate , which was added in the Commission class. The uninitialized value for a DateTime variable is equivalent to DateTime.MinValue . In GetObjectData if TransmittedDate is uninitialized, the current date and time are serialized to the stream.

The basic idea is that you implement GetObjectData to serialize the object when Serialize is called. When deserialization is needed, it is implied that you are creating a new object. The Visual Studio .NET help documentation is misleading because it doesn't show the constructor ”the symmetric operation to GetObjectData ”to deserialize the object.

As I mentioned, XmlSerializer won't use this code, so you don't need it if your intention is to return the typed collection from a Web Service. However, a BinaryFormatter will use the ISerializable code. (You can read more about BinaryFormatter in the VS .NET help documentation.)

Returning a Collection from a Web Service

Implementing the typed collection is the hardest part of returning typed collections from an XML Web Service. Keep in mind that you will get a flattened version of the collection ”but you won't even get that if you don't include the ICollection.Add method in your typed collection.

Outside of a Web Service, the Add method implemented by CollectionBase suffices. However, when you return a typed collection from a Web Service, .NET seems to be more particular. Suppose, for instance, that you forgot the Add method. When you build and browse the Web Service, you will get an InvalidOperationException (Figure 14.5) indicating that you did not implement the ICollection.Add interface method. So be savvy when creating Web Services ”implement the ICollection.Add method.

Figure 14.5. An InvalidOperationException occurs when you run a Web Service that returns a typed collection without the ICollection.Add method.

graphics/14fig05.jpg

When you declare the variable that will receive the data from the Web Service that returns a typed collection, it is important to note that the data type you declare will not be the collections typed. Instead you will declare a variable that is a System.Array of the collected type. For our example, the client would declare an array of Commission objects to receive the value from GetCommissionsList . This subtlety is illustrated in the next section in Listing 14.13.

Refer to TypedCollection.vbproj for the source code that returns an instance of CommissionsDataList . You can use the build and browse method described in Chapter 13 to see how the data in the typed collection is serialized and returned from the Web Service.



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

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