Hibernate and .NET 2.0 strongly-typed DataSets do share some features in common:
- An xml document (the DataSet) is used to determine the mapping rules. (It is also convenient--although much more data-driven than object-model driven that the mapping xml can be created in a designer mode by dragging tables onto the screen and linking relations between them.)
- This xml document also automatically generates all of the objects, as well as a separate sub-namespace (think Java package) containing the repository classes for populating the objects.
- The objects, object collections, and associations are all mapped to the database.
- The objects are persisted to and from the database using repository classes (known as table adapters in .NET DataSets.) These classes are generated in a separate sub-namespace (package).
- The DataSet model is a very strongly data-driven model. This makes it difficult to develop a true object model as you can in Hibernate. As a result, there is no real control to build the object model according to best practices or design patterns, as the model is entirely generated from the database-driven DataSet. The model also uses database naming conventions: the collection is named with a "DataTable" suffix (eg. the Person collection would be "PersonDataTable") and the contained objects are named with a "Row" suffix (the object would be "PersonRow").
- There is no HQL-style query language. You are directly addressing a specific database, and, of course, that database is almost exclusively Microsoft SQL Server. And while you can theoretically use queries, stored procedures are considered the norm.
- You cannot populate an object graph from a single query/stored procedure. You have two less-than-ideal choices:
- You can create a query/sproc that contains joins, and bring back tabular data, which is obviously no longer a true object graph, it's just a result set. (I don't use this option.)
- The second choice is to create SEPARATE queries in each table that forms a part of the object graph. For example, if you wanted to know about the Person, the person's Invoices, the person's Accounts, and the person's Appointments, all of which form the object graph, you would create separate repository queries (by personID) for EACH of the 4 objects, and you would then make 4 calls to populate each object/collection. This also means that you must populate them in the right order (parent first, then child) to prevent constraint errors.
- You can create a query/sproc that contains joins, and bring back tabular data, which is obviously no longer a true object graph, it's just a result set. (I don't use this option.)
- The most difficult is that there is not a single mapping of the object model in a single namespace (package). Instead, a typical .NET project may contain multiple DataSets, often with redundant data tables across DataSets. The generated classes and related repository(table adapter) classes are DataSet specific, so a Person object may end up redundantly as the PersonRow class in MANY different DataSets. And, because they are in distinct DataSets, they cannot be assigned to each other. Finally, a change in a table of the database must be implmented not in a single location, but redundantly across all DataSets that reference the particular data table.
- I have had to extend the generated objects with additional functionality in order to link each of the objects in the object graph to the same transaction. And since each of the auto-geneated repository classes do not share a common interface, I have had to force interface implementation. I have achieved this with a .NET convention called "partial classes" (files where you can "add to" (not extend!) an auto-generated class, so that when the auto-generated class gets regenerated, your modifications are not overwritten. It's hackish, but it works.)