Reducing SQL Lookup Tables and Function Properties in NHibernate

Posted by: Steven Smith, on 06 Oct 2008 | View original | Bookmarked: 0 time(s)

One of the points made in Jeffrey's agile development boot camp last week that struck a chord with me was that many database-centric designs have lookup tables that aren't really just data.  That is, tables that hold values such as statuses that, if modified, can easily break existing code or won't function as expected without the addition of code.  We have a lot of these in our codebase, and we've been struggling with the questions of how to address these values in our system.  Do we use Enums?  If so, do we try and keep the enums in sync with the database values?  If not, do we want to use magic numbers/strings to refer to these codes?  It's a real PITA to work with these things and really the only reason they're in the database at all is so we have some referential integrity locking down the allowable values of the status column(s).

You can avoid this problem if you push the status logic into objects and don't rely on the database to implement referential integrity for you.  Create a Status class that has all of the logic necessary for rendering a status code in a variety of ways (for instance a short code, a long name, and a description, all of which are difficult to implement on Enums without attributes or other extension approaches).  Have your primary class reference the status via composition (e.g. a property).  Then, in your persistence layer, you can store just the short code value (or integer key) of the class, but without the requirement that there be a lookup table involved.  If referential integrity is of great importance, a constraint can easily be applied to the database.  The constraint would then be updated with the build at the same time as new values are added to the Status class.  The status can also be used similarly to an Enum assuming it has some static properties representing its value instances, like so:

   1: public class WorkOrderStatus
   2: {
   3:     public static readonly WorkOrderStatus Assigned = new WorkOrderStatus(
   4:         "ASD", "Assigned", "Assigned", 2, true);
   5:  
   6:     public static readonly WorkOrderStatus Cancelled = new WorkOrderStatus(
   7:         "CAN", "Cancelled", "Cancelled", 5, false);
   8:  
   9:     public static readonly WorkOrderStatus Complete = new WorkOrderStatus(
  10:         "CMP", "Complete", "Complete", 4, false);
  11: ...
  12: }

NHibernate can map this status directly if you create a WorkOrderStatusType class that inherits from NHibernate.Type.PrimitiveType and overrides its methods.  The mapping then is simply:

   1: <class name="WorkOrder">
   2: <property name="Status" 
   3:     type="WorkOrderStatusType, Infrastructure" 
   4:     not-null="true" />
   5: ...
   6: </class>

One very common pattern I've noticed as software evolves is the migration from simple boolean state codes on objects to multi-value states and eventually to the need for some kind of an event log.  In the example above, perhaps originally we started with a WorkOrderStatus that was simply a boolean with states for Complete and Incomplete, and it has since moved on to include the states listed above (and several others).  Now the customer wants to know when the status changed, and by whom, so they can track the WorkOrder through its workflow.

Typically this requires a bunch of changes in the database and some application logic to get the current status from the event log as the most recent one.  Further, the WorkOrder might expose dates for LastUpdated and CompletedDate which should be tied to this same event log, though prior to adding the event log they were set in application code when these state transformations occurred.  This is an example wherein NHibernate's support for functions can be very helpful (see also this post by Ayende).  Assuming there is an Event table which has columns for BeginStatus and EndStatus as well was a DateTime column, date properties for the WorkOrder could be mapped like so:

   1: <class name="WorkOrder">
   2: <property name="LastAssignedDate" formula="(
   3: select max(e.DateTime) 
   4: from Event e 
   5: where (e.WorkOrderId = Id)
   6: and (e.EndStatus = 'ASD'))"/>
   7: <property name="LastUpdatedDate" formula="(
   8: select max(e.DateTime) 
   9: from Event e 
  10: where (e.WorkOrderId = Id))"/>
  11: ...
  12: </class>
The nice thing about this approach is that there are fewer tables in the database that are dedicated to mere lookup data.  In this case, we've eliminated the need for a WorkOrderStatus table that might have had columns like ID, Name, Description, ShortCode, SortBy, and perhaps other properties.  Keeping the database more streamlined makes it easier to comprehend and easier to change, and keeping this logic in our classes rather than in the database makes our code easier to change and easier to test.  If the raw SQL in the mapping file is a red flag for you, replace them with SQL Functions (which Ayende covers in his post).

Advertisement
Free Agile Project Management Tool from Telerik
TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.
Category: Data | Other Posts: View all posts by this blogger | Report as irrelevant | View bloggers stats | Views: 2396 | Hits: 299

Similar Posts

  • Calling UDFs from Entity Framework - Not what you might have expected more
  • Implementing SqlBulkCopy in Linq to Sql more
  • How to debug a stored procedure in your Sql Server 2005 more
  • Upload Images To SQL Server By Way Of An ASP.NET Web Form more
  • Putting Function.apply() to Work more
  • SQLite, ADO.NET, Prepared Statements, Transactions and Enterprise Manager more
  • How to hire a programmer - Part 2 - Improve this code more
  • Examining ASP.NET 2.0's Membership, Roles, and Profile - Part 7 more
  • Examining ASP.NET 2.0's Membership, Roles, and Profile - Part 6 more
  • Determine whether a SQL Server exists .. more

News Categories

.NET | Agile | Ajax | Architecture | ASP.NET | BizTalk | C# | Certification | Data | DataGrid | DataSet | Debugger | DotNetNuke | Events | GridView | IIS | Indigo | JavaScript | Mobile | Mono | Patterns and Practices | Performance | Podcast | Refactor | Regex | Security | Sharepoint | Silverlight | Smart Client Applications | Software | SQL | VB.NET | Visual Studio | W3 | WCF | WinFx | WPF | WSE | XAML | XLinq | XML | XSD