Tests of the Week
Now for what many of you surely consider the highlight of each article: the test results. As usual, I have a service-layer class (business facade layer) for each test. You can see the service-layer classes in Figure 3.
Figure 3 The two examples of the service-layer class.
The service-layer classes inherit, as usual, from MarshalByRefObject. This is because they should be suitable as root classes when used via Remoting.
NOTE
Note that the second method in the class for the wrapped DataSet returns OrderWrap2. That wrapped DataSet class has only an OrderLines DataTable. That's one solution to the problem when fetching only OrderLines from the database.
Result of the Tests
I've added the tests of the data container options to the results tables.
Again, I'm using the DataReader as a baseline. Therefore, I have recalculated all the values so that I get a value of 1 for the DataReader. The rest of the data containers have a value that is relative to the DataReader value. That makes it very easy to compare; the higher the value, the better.
Table 1: Results for the First Test Case: Reading One Row
1 User, in AppDomain |
5 Users, in AppDomain |
1 User, Cross-Machines |
5 Users, Cross-Machines |
|
DataReader |
1 |
1 |
1 |
1 |
Untyped DataSet |
0.6 |
0.6 |
1.4 |
1.7 |
Typed DataSet |
0.4 |
0.5 |
1 |
1.1 |
Wrapped DataSet |
0.5 |
0.6 |
1.3 |
1.7 |
Hashtable |
0.9 |
1 |
3.5 |
3.9 |
Table 2: Results for the Second Test Case: Reading Many Rows
1 User, in AppDomain |
5 Users, in AppDomain |
1 User, Cross-Machines |
5 Users, Cross-Machines |
|
DataReader |
1 |
1 |
1 |
1 |
Untyped DataSet |
0.5 |
0.6 |
6.9 |
9.7 |
Typed DataSet |
0.5 |
0.5 |
6 |
8.6 |
Wrapped DataSet |
0.5 |
0.6 |
6.6 |
9.6 |
Hashtable |
0.8 |
0.9 |
17 |
23.5 |
Table 3: Results for the Third Test Case: Reading One Master Row and Many Detail Rows
1 User, in AppDomain |
5 Users, in AppDomain |
1 User, Cross-Machines |
5 Users, Cross-Machines |
|
DataReader |
1 |
1 |
1 |
1 |
Untyped DataSet |
0.5 |
0.5 |
6.1 |
8.5 |
Typed DataSet |
0.4 |
0.4 |
5.1 |
6.9 |
Wrapped DataSet |
0.5 |
0.5 |
5.8 |
8 |
Hashtable |
0.8 |
0.9 |
16.2 |
19.6 |
As you can see in these tables, the wrapped DataSet is very close to the untyped DataSet in performance. The wrapper I have created is very thin, so that similarity in performance is to be expected.
What might be more surprising is that the Hashtable is so close to the DataReader in the tests within the AppDomain; there's very little overhead. Even more surprising might be to see how efficient the Hashtable is across machines; it's compact and it's lightweight. But again, performance isn't everything. Let's compare a couple of other factors as well.
Highly Subjective Results
My "highly subjective results" table needs to be expanded with the two options we have tested in this article. In Table 4, I have assigned some grades according to the qualities discussed in the first part of this series. A score of 5 is excellent, and a score of 1 is poor.
Table 4: Grades According to Qualities
Performance in AppDomain /Cross-Machines |
Scalability in AppDomain /Cross-Machines |
Productivity |
Maintainability |
Interoperability |
|
DataReader |
5/1 |
4/1 |
2 |
1 |
1 |
DataSet |
3/3 |
3/3 |
4 |
3 |
4 |
Typed DataSet |
2/2 |
2/2 |
5 |
4 |
5 |
Wrapped DataSet |
3/3 |
3/3 |
3 |
5 |
4 |
Hashtable |
5/5 |
5/5 |
2 |
2 |
2 |
As usual, I'd like to say a few words about each quality grade.
Performance
I've already talked about the performance behavior of the two options in this article. The wrapped DataSet is very similar to the untyped DataSet. The Hashtable, on the other hand, is much more efficient and lightweight. It's almost twice as efficient as the wrapped DataSet in an AppDomain and almost three times as efficient across machines. Again, remember that these tests are end to end. If you isolate, for example, the serialization/deserialization, the difference is even bigger.
NOTE
As you might remember from Part 2, I discussed a workaround for bad performance because of how DataSets are serialized. Using a wrapped DataSet is a great way of dealing with the problem; I'll get back to this in Part 5 of this series. I will also show results for wrapping a Hashtable in that article. That's a very good way to avoid the hack feeling with a Hashtable and also the serialization issue with the DataSet.
Scalability
According to the definition of the scalability quality that I established in Part 1, scalability goes pretty much hand in hand with performance here. That's the reason for the same grades.
Productivity
When you wrap a DataSet, you get a lot of functionality for free, but you do have to create (and support) the interface on your own. A tool will help tremendously here. Without it, your productivity will suffer. That's the reason for the score of only 3. Pretty much the same is true for the Hashtable, but you get a little less for free and there is more code to write.
Maintainability
If you succeed in creating a good small interface for the wrapped DataSet, then maintainability will be very good. In the case of the Hashtable, the loose typing doesn't help in this aspect. It's not just the columns that are loosely coupled; it's how to reach related rows, too.
Interoperability
If the wrapping solution wraps a DataSet, you can easily use the functionality of the DataSet for interoperability. (But that might hurt encapsulation.) The Hashtable isn't very good at interoperability, and you have no control over it. The only thing that keeps it from getting a score of 1 here is that you can probably expect the receiver to have a Hashtable implementation to which the receiver can rehydrate the stream.
NOTE
As a matter of fact, I think the Hashtable was very close to a lower grade for both maintainability and interoperability.
If you just add the grades (and choose AppDomain or cross-machines), you will find that the wrapped DataSet is the best so farnot by far, but still best. On the other hand, I again want to stress that you should use your weights according to which qualities that you find the most important.