A Little History – Getting to a decent data pattern
Years ago, I learned how to write N-Tier applications. In the apps I wrote in those days, a data tier would retrieve data from a database - probably by calling a stored procedure. ADO (and later ADO.Net) packaged the retrieved data into a DataSet, which contained one or more DataTables. A DataTable looks a lot like a table in a database. It presents the data as rows and columns (i.e., in rectangular format). So if a DataTable contains 5 rows, each row will have the same columns.
In my early N-Tier applications, I would pass the DataSet to other application tiers - such as a business tier to apply business rules - until it ultimately arrived at the user interface tier, where the user could consume this data. Microsoft encouraged the practice of sharing a dataset among all application tiers by supplying controls that bound easily to a DataSet.
These DataSets and DataTables are still widely used today. But the practice of passing them from the Data Access layer up to the User Interface Layer presents problems. DataSets tend to look very much like the databases from which they were retrieved. And databases are typically not built with the user interface or with business rules in mind. A database has different goals, such as to optimize storage or reporting. By contrast, a user interface should present data in way that it is easily understood, consumed and manipulated by the end user.
So, a few years ago, I changed my strategy. In my next architecture pattern, the Data Layer of my application continued to retrieve data from a database in the same way and to produce DataSets and DataTables as before. But I also created entity classes that more closely resembled objects as my business saw them. Rather than holding data in rows and columns, I stored it in objects and properties. Some of these properties pointed to another object or to a collection of objects, allowing my data to to take on a hierarchical format, rather than the rectangular shape encouraged by database tables. For example, a customer object contained properties for FirstName and LastName; but it also contained a property of a set of orders. I could drill into that set and examine an individual order and I could drill into that order and examine individual lines of that order. I found these business entities easier to understand, easier to work with, and easier to explain to non-technical people. The hierarchical nature that exists between objects is more obvious in the entity representation. Entity representations of data are also more easily consumable in an object-oriented language, such as C#, because entity properties are strongly-typed.
I bought into the pattern described above for all enterprise software that I write. The only problem with this pattern is that it required me to write a lot of code. If I add a new table to my database, I need to write the following code
- Stored procedures to add, update, read and delete data in the table.
- Data Access methods to add, update, read and delete the data in the table.
- An entity to represent the table
- Code to map a set of rows in a DataTable into a set of entities.
I needed to write the code described above over and over – for each of my entities. The time spent writing this “plumbing” code could be spent addressing the customer’s business problems. Worse, any time I added a column to a table, I needed to modify code in each of the modules listed above to take this change into account.
Object Relational Mappers
An Object Relational Mapping (ORM) tool can alleviate this problem by writing part of this code for you and eliminating the need for other parts.
An ORM takes care of mapping between Entity objects and database objects. By taking much of this work away from the developer, he can focus on writing business logic.
An ORM typically consists of three parts: a data layer, a mapping layer, and an entity layer. The data layer retrieves data from the database, storing it in traditional data objects, such as DataSets and DataTables. The entity layer defines the objects that are exposed to the object-oriented system. The mapping layer (as you might guess) maps between these systems. The figure below shows these layers.
Depending on the particular ORM framework, these layers might be implemented in code, in configuration files, or using a defined naming convention. In any event, there is typically much less code involved than in
Advantages of an ORM
As noted above, an ORM makes it easier transform data into entities that more naturally fit an object-oriented system.
An ORM can reduce the amount of code you write by providing conventions and configurations (and sometimes, a user interface).
The abstraction layer between the database and the application makes it easier to switch databases. The developer needs to worry only about the data layer and most ORM frameworks help with this. It’s not common to replace one database with another, but it is common to produce a commercial application that supports more than one data platform.
Many ORM frameworks include lazy loading (the ability to defer query execution until an entity is used) and LINQ support, which makes them more flexible.
ORMs make it simple to update data. Calling a save method on an entity object takes care of all the database commands required by ADO.Net.
Transactions are typically handled automatically by an ORM framework. If an entity is saved, all associated entities are saved at the same time and the database update occurs within an atomic transaction.
Hibernate is an ORM framework created ten years ago for Java. A few years later, Hibernate was ported to .Net as NHibernate. This remains the most mature ORM on the .Net platform and it continues to be enhanced. Many more .Net ORMs have appeared over the past few years, such as Subsonic, LLBLGen, and LINQ to SQL. Microsoft is currently investing heavily in the Entity Framework which still lags NHibernate in features but shows a lot of promise.
These days, it’s rare for me to write any ADO.Net code. My last three projects involving data access have used NHibernate or Entity Framework. As a result, I spend less time writing code and more time writing business logic.