Δευτέρα, Μαρτίου 07, 2005

Why did I build KT Data Components?

My components I refer to in my previous post are called KT Data Components. They are actually just a ClientDataSet and a DataSetProvider descendant. The reason I began this effort is because DataSnap/midas didn't cover my needs in a particular point: many-to-many relationships and I tried to enhance functionality so that I could use these components the way I would like them to be used. When I began writing the components I was unaware (and I still have little knowledge) of ADO.NET. My motivation became stronger as I read some of Dan Miser's older BDN articles and used midas essential pack components
In particular, the use of clientdataset as lookup tables seemed very convenient, as was the technique of wrapping multi-clientdataset updates in a transaction. To be more specific, I' ll start with this:
All Datasnap documentation I've read about says that the presentation layer of a multitier application should be represented by a thin client, that could be able of working (optionally) as a briefcase model. This way business rules and logic should be on an application server and the client side would only be able to represent the user interface. TClientDataSet was designed with this in mind. But, what impressed me from the beginning is how master-detail relationships are handled in Datasnap. The forming of one object structure (ClientDataSet with master and nested datasets) in client side and simply having to manipulate the MasterCds is really a pioneer technology, even for contemporary standards. The nested dataset structure even if it can handle m-d-d-... relationships very well, it has an obvious drawback. It isn't able to handle many-to-many relationships in the same elegant way.
For example, let's say we have a database where we have a categories table and and items table and we would like an item to belong to more than one category. We should create a third table (ItemsCat) to represent the relationship and possibly add some kind of referential constraints to preserve data integrity. How can this kind of data be manipulated using Datasnap? I could say that there are the following ways:
  • We could form a master- detail relationship on application server using as master the Categories table and as details the ItemsCat table. We could do that also between the Items (master) and ItemsCat (details) table, and then fetch to client side two nested dataset structures having two different views of data. This solution is not acceptable though as we consume network bandwidth having to move ItemsCat twice
  • We can create a join query on these tables (in the back-end database) and "provide" the client with the data of this query. The user then, is able to manipulate the query and the provider should handle all the steps needed to update the join. This is the most used approach, although this way the client is "really" thin and users are now accustomed to better user interfaces
  • We could create three DataSetProvider components for each table and three clientdataset components in client side and form the link in clientside (using mastersource and masterfields properties of clientdataset). This is reasonable of course, only in cases where all tables are not very large, since this way we oppose the client/server theory of building applications (i.e. fetching only data that are necessary for user to interact with)
  • Suppose Items table has 100000 records and categories table has 100 records. I believe this is quite frequent (not all tables of a database have millions of records). We could form on application server a master- detail relationship between Items (master) and ItemCat (detail) and fetch as many records as appropriate to client (through clientdataset Packetrecords property) in a nested dataset structure form. We could use a separate TDataSetProvider component on server hooked to categories table and create the respective client dataset (in client app). Then we could easily have this table CategoriesCds work as a lookup table (form a lookupfield on nested ItemsCatCds dataset). This is the approach (more or less) proposed by Dan Miser when he refers to static lookup tables.
I believe that the last two solutions (and the last one in particular) are the best way to create a "not so thin client" application. But, there comes an apparent problem . Ok, we say that categories table is static (or rarely changing), but what happens, if for example a user deletes a record from this table (Cds)? This is up to the developer to decide of course. We have (in client side) two clientdataset components (a simple and a nested structure). I believe that there should be some kind of easy way to handle problems like that and that's why I built my components. TKTClientDataset component, straightly inherited from TClientDataSet simply has a new string property (ForeignKeys) that defines what happens when a user updates this component's primary key. Unfortunately, since my component is rather simple I have bound Fields[0] and defined it implicitly as the primary key. In addition, all foreign keys and Fields[0] (primary keys) should be of TIntegerField type. But anyway, it suits my needs at this moment and covers the problem noted above. If I delete a record from CategoriesCds table, then the foreign key field can be set to null, or automatically be deleted (cascade deletes), according to the property attributes I've set in design time.
And what about, applying updates. These are handled automatically by my BatchApplyUpdates method, they are wrapped in a transaction (as appropriate) and they are applied in the correct order (i.e. if I had cascade deleted in the previous example, then ItemsCat deleted record would have been applied first and Categories deleted record would follow).
Of course, there are a lot of things to be done so that KT Data Components become more elegant. First of all, I should make a new property called primary key that denotes the field that should be used as such (and cutoff dependency on Fields[0]). Second, may be I should add support to all field types (not just TIntegerField). Third, I have to find a way to handle circular references and many more...
On the other hand, I don't know if I will be able to achieve these goals (at least without help) since the components, as they are, cover all my needs at the time. Besides, I've heard that .NET DataSet may be handles all these issues as we are talking and not a lot of developers might be interested in Datasnap anymore. I'll give it a try though and if anyone is interested in asking anything relevant, I would be very glad to answer. In addition, any suggestions, or help are welcomed too!