Dot Net Tricks

Articles about .NET, ASP.NET, C#, Object Oriented Programming and Agile Methodologies
Welcome to Dot Net Tricks Sign in | Join | Help
in Search

Software Theosophy

Objects are NOT Tables

I’ve been doing a lot of research into O/R Mappers lately.  I’ve looked into LLBLGen, IdeaBlade, NHibernate, NetTiers, Paul Wilson’s (WORM) and a few others.  I’ve noticed a couple of recurring themes about most of the O/R Mappers.  Many of them are not merely O/R Mappers, they are code generators.  They look at your databases schema, map each table into a class, map each non-key field into a class field and corresponding property, and map each foreign key relationship to a property referencing another object.  They build a bunch of classes that for the most part are nearly identical to your table structure.  Every Table basically becomes an Object.

 

This works fine on small applications and is likely to get you some working code very quickly.  Many simple objects do map nicely from tables in this manner.  But it’s not necessarily always right either.   Having all your objects generated in this way often removes a lot of the benefits of the Object-Oriented paradigm and 3 layer design.  It encourages developers to put business logic into the presentation layer as well as other problems.

 

Un- Encapsulation

 

Encapsulation is one of the most basic features of OOP.  Unfortunately, many O/R Mappers create objects that break encapsulation.  Let me give you a simple example using a content management application with some approval functionality.  Assume we have a table called Content which has the following fields: ContentID (an identity field and PK), StatusID (an FK to a table of statuses such as Draft, Submitted and Approved, Title (the title you want for your content, DateCreated and HTML which is a Text field holding the Html for the page.

 

ContentDiagram.gif

 

Now assume that we use an O/R Mapper that generates a bunch of classes for me in the way I’ve described above.  The Content class will have the following properties: ContentID, Status (which will reference a Status object), Title a string, DateCreated a DateTime and Html also a string.

 

Most likely, all the fields except ContentID will become properties with both "get" and "set" code.  Some O/R Mappers/Generators such as IdeaBlade will allow you to map certain fields as Read Only properties, but many do not.  So the DateCreated property may end up generated with a mutatator.  Unfortunately, it is likely that we’ll want the DateCreated property to only be set when the object is about to get persisted to the database for the very first time.  I probably don’t want to do this in the presentation layer as it represents a business rule.  I will want this field set by the business object itself as it is being persisted, or perhaps as a default value in generated by the database.  Either way, having DateCreated have both get and set blocks makes no sense and violates encapsulation. This is bad. 

 

Next, let’s look at the Status entity.  Most O/R Mappers will generate a full fledged class from the Status table.  However, if the logic about the status is simple or if the records in the status table are fairly static, this might not make any sense either.  I may want the Status to be an enum and put its 4 possible states in the Status list (Draft, Submitted, Declined and Approved) in my application code, so I can reference these values in my code.  I may not need any logic to persist new Statuses into my database, nor any reason to have a Status object.  Sometimes it might make sense to make this into a class but other times it just makes the business layer more complicated.

 

Now let's add a bit of additional functionality.  Let’s assume that we want to keep a History of who changed the status for a piece of content and when it changed.  So we create another, related table called ContentHistory, with the following fields: ContentHistoryID int (the PK), ContentID int (FK), StatusID int (FK), UserID int (which would be an FK to some User table)  and DateCreated.  Our entity diagram will now look like this:

 

ContentDiagram2.gif 

 

Now our code generators are most likely going to create a ContentHistory class and a property in our Content class related to it.  The Content class will likely have a strongly typed collection or an array list of ContentHistory objects.  Each of the properties in the ContentHistory class will probably have both get and set accessors, which as we saw earlier might not make any sense for every field.

 

In fact, if we were to create a use case or user story (to borrow an XP term) for the ContentHistory, it might go something like this:  “When the user approves, declines or otherwise changes the status of a piece of content, the system will create a record of history about the content, and record the date of the status change, the user who made the change, and the status the content was changed to.”  This use case represents business logic.  We could certainly do all this in the presentation layer, but we probably don’t want to.  We’ve been told over and over that the business objects should enforce business rules such as these.   I prefer to encapsulate this logic into a single method into the Content class like the following:

 

public void ChangeApproval(StatusEnum newStatus, User user)

{

      this.status = newStatus;

 

      ContentHistory history = new ContentHistory();

      history.DateOfChange = DateTime.Now;

      history.User = user;

      history.Status = newStatus;

 

      this.HistoryItems.Add(history);

}

 

This method changes the current status of our Content object, and records the status change into the HistoryItems collection, giving it a user, a timestamp and the new status object.  I want any changes in status to go through this method and only this method, so that my business rules are enforced.

 

I prefer strongly typed collections (or possibly generics in .NET 2.0) so my Content object would have a HistoryItems property which is Read Only and of type ContentHistoryCollection.  Rather than exposing the ContentHistory collection’s Add/Remove methods, I would keep those methods private, because I only want history items to be added thru my ChangeApproval() method above.  I might even make the constructor of the ContentHistory class internal, so only the Content and other classes in the same assembly can create History records.  Finally, I would also make the Status property of the Content class Read Only as well, so the Status can’t be changed without going thru the ChangeApproval() method and recording the change in the History.  Its all in one process which cannot be bypassed.

 

The above is just a very basic example of using encapsulation in OOP.  All my business logic is enforced by the objects themselves, not the UI or some other layer.  I’m not allowing the presentation or other objects the break the business rules by changing the status or altering the history thru some other, “backdoor” process.  Other objects cannot change the data inside the objects, such as the Status, DateCreated and HistoryItem collection, without going thru the methods designed to enforce these business rules.

 

However few O/R Mappers or Code Generation techniques give you this level of flexibility.  They are likely to make every property public, make every collection’s Add/Remove methods public and perhaps not even make strongly typed collections at all.  This means that it’s very tempting for another developer to break the business rules, or to put business logic directly into the UI.

 

Bad Relationships

 

Most of the O/R products do not handle relationships between objects in a very robust manner.  For example, many do not seem to handle many-to-many relationships well at all.  They do weird things like generate individual classes for junction tables which exist in relational databases only to represent many-to-many relationships.  For example, if I build a catalog website with a situation where a Product can be in multiple Categories, and Categories can have multiple Products in them, I will likely have a junction table representing this two-way relationship called ProductToCategory.  This table will only have the foreign keys ProductID and CategoryID.  However, I don’t necessarily need a ProductToCategory class.  In the object world, a Product object can directly hold references to multiple Category objects using a collection, and a Category object could hold a collection of Products. This entity, ProductToCategory is essential in RDBMS’s, but superfluous in your class hierarchy. 

 

On the topic of relationships between objects, most of the relationships we’ve been talking about are forms of Composition.  That is, one object is embedded inside another object and not shared with any other.  But other relationships exist between objects, as they do in the real weird, such as Inheritance, Aggregation, and various forms of Association.  These relationships can and should be implemented in different ways within your object model.  With Aggregation and Composition, a field and an accessor Property are used.  Inheritance is built in to most .NET languages and is represented very differently.  Rather than the parent object having reference to the child, the child gains all the functionality and interface of the parent and can add it own.  Associations may have a variety of implementations and may not even expose the relationships publicly outside the object. 

 

Unfortunately, most O/R Mappers implement every relationship between every table as a form of Aggregation or Composition.  Each relationship is created by having one object have a property exposing a related object.  This results in a very clunky, unnatural object model.  You lose the ability to implement any other kind of relationship.  While inheritance can be overused at times, sometimes it makes a lot of sense.  At other times, you may want associations where one object generates other types of objects or takes them and does something without referencing them in a local field or property. 

 

Good O/R Mapping should allow you to represent your objects as objects regardless of the data structure.  In his book Object Thinking, David West says that objects are defined by behavior, not the data they hold.  At times, objects and tables might be superficially similar, but they usually are not the same. 

 

Not every table deserves to be promoted to "object-hood."

Published Monday, September 05, 2005 7:42 PM by Fregas
Filed Under: , , , ,

Comments

 

Joe Reddy said:

I know I am late to this discussion, but I just had to comment.
You are right on the money.
For me, OR Mappers (which I generally do myself with little to no pain) should be nothing more than creating an abstraction of meaningful database chunks which might be one table at a time.

Currently I can auto generate a class for any table. However, this is not a business class; rather it is just a dumb DTO. I create my business objects from these guys…often two or three at a time, often just one. Either way I treat them separately.

Thanks,

Joe

PS
I am glad I found your blog. We have very similar backgrounds, thus I bet we have very similar opinions on things. I bet you would enjoy this list I made up sometime last year...(Top 10 signs you might be a code writing hack…) -- http://swcses.eponym.com/blog/_archives/2006/3/10/1814430.html
March 7, 2007 11:58 AM
Anonymous comments are disabled

About Fregas

Craig is currently the Lead Developer in Fort Worth, Texas for Enilon Group, a web development firm. He has been programming since 3rd grade (using the Commodoore PET) and professionally for the past 7 years. He has written several articles for ASPToday.com and co-authored the book "Beginning Web Programming using VB.NET and Visual Studio .NET" Currently, his favorite programming language is C#, but he has programmed in Visual Basic, T-SQL, Ruby, ColdFusion, ASP 3.0/VBScript, ASP.NET, Javascript, Java and even Pascal. Besides programming, Craig is best known for his cooking and his somewhat offbeat sense of humor.

This Blog

Post Calendar

<September 2005>
SuMoTuWeThFrSa
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

Syndication

Powered by Community Server, by Telligent Systems