Click here to Skip to main content
15,883,979 members
Articles / Web Development / ASP.NET

Persistent Object Management in Less than 300 Lines of Code

Rate me:
Please Sign up or sign in to vote.
3.93/5 (14 votes)
8 Oct 2007CPOL4 min read 87.7K   590   70   35
Presenting a lightweight method to cut out most of the repetitive work of creating data classes.

Introduction

There's a lot of repetitive work associated with the writing of data classes within the data access layer of an N-tiered architecture. Apart from adding a column to the relevant database table, adding a field to the class involves adding a private variable to hold the field value, a property to expose it to other classes, and several lines to make sure the field is included in insert, fetch, and update operations. If you're using stored procedures (very silly for CRUD queries) there are several more modifications to make. This is dull work, I'm sure you'll agree. Presented here is a lightweight method for taking away all this tedium, such that you only have to add a getter/setter property in order to add a field to a data class.

The technique involves abstracting SQL query writing and execution, and variable storage in data access classes to a base class. In the implementation presented here, I've named this base class "PersistentDataObject". The "less than 300 lines of code" referred to in the title of this post are the lines of code in this base class, the database access class it uses, and a couple of exception classes used as markers to add clarity to exception origins. The complete code is included at the end of the post.

Background

I conceived this idea, before my .NET days, while working on a monolithic PHP project. After writing hundreds of simple CRUD queries that were all basically the same, I realized that it would be a simple matter to abstract all that query writing in such a way that I would never have to write such a simple query again. That was over a year ago. Now I'm a fully paid up ASP.NET developer, and I think the code has reached a level of refinement at which it is ready to be presented to the world. I use the code presented here in every system I build and I love it. Maybe you will too.

Using the Code

Here is a simple Person class created by extending PersistentDataObject:

C#
using System;

public class Person : PersistentDataObject
{
 public Person() : base("Person", "PersonId") { }
 public Person(int id) : this() { Load(id); }

 public string FirstName
 {
   get { return Get<string>("FirstName"); }
   set { Set("FirstName", value); }
 }
 public string LastName
 {
   get { return Get<string>("LastName"); }
   set { Set("LastName", value); }
 }
 public DateTime DateOfBirth
 {
   get { return Get<DateTime>("DateOfBirth"); }
   set { Set("DateOfBirth", value); }
 }
 public int? LuckyNumber
 {
   get { return Get<int?>("LuckyNumber"); }
   set { Set("LuckyNumber", value); }
 }
}

Configuring the Data Object Class

The only configuration values necessary for the class are name of the table from which the data is to be retrieved, and the name of the unique id column within that table (assumed to be an integer column). These are passed to PersistentDataObject in the constructor.

C#
public Person() : base("Person", "PersonId") { }

Fetching Data from the Database

The Person class shown above provides a constructor that accepts an integer identifier as an argument. This constructor delegates to the Load method of PersistentDataObject to fetch all data from the database for the row with the specified identifier.

C#
public Person(int id) : this() { Load(id); }

An overload of the Load method is provided that takes a DataRow as an argument. This allows the object to be populated with data from a DataRow that has already been fetched from the database.

Exposing Column Values as Properties

Column Values are exposed by delegating to the Get and Set methods of PersistentDataObject. The Get method takes a string argument indicating the column from which the value should be retrieved. Similarly, the Set method takes a string argument to indicate for which column the value is to be set. This example shows a property exposing a DateTime column:

C#
public DateTime DateOfBirth
{
get { return Get<DateTime>("DateOfBirth"); }
set { Set("DateOfBirth", value); }
}

Note that the Get method uses generics to identify the type of the value that should be returned. The type specified should be the appropriate type dependant on the type of the column.

Handling Null Values

Nullable value types come into play in order to handle columns that allow null values. This example shows an integer column for which null values are allowed exposed as a property:

C#
public int? LuckyNumber
{
get { return Get<int?>("LuckyNumber"); }
set { Set("LuckyNumber", value); }
}

Note that as string is a reference type, it does not need to be specified as nullable to accept null values.

Inserting, Updating, and Deleting

A Save method is provided that handles both the inserting into, and the updating of objects in the database. If the Save method is called on an object that does not yet exist in the database, then an insertion is performed. If the object does already exist in the database, an update is performed. This code snippet shows an instance of Person being created and a record for it being inserted into the database:

C#
Person person = new Person();
person.FirstName = "Nick";
person.LastName = "Higgs";
person.DateOfBirth = new DateTime(1983, 2, 7);
person.Save();

A Delete method is provided to allow the object to be deleted from the database. This code shows an object for an existing person record being instantiated and then deleted from the database:

C#
Person person = new Person(51);
person.Delete();

Closing Remarks

That completes the explanation. I've found that this technique not only takes most of the repetitive work out of writing data objects, but provides a solid foundation to a data access layer, regardless of the complexity of the system.

History

Modified from an article posted on my (now discontinued) blog
http://developmental-issues.blogspot.com/2007/04/simple-or-mapping-in-less-than-300.html

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United Kingdom United Kingdom
Since graduating from the University of Sheffield with a Masters degree in Software Engineering in 2005 I have been woking as a professional web developer for a Sheffield new media company.
ASP.Net is currently my primary technology, but I also have substantial experience with PHP and Flex and various Java technologies.

Comments and Discussions

 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
mr_lasseter9-Apr-07 15:44
mr_lasseter9-Apr-07 15:44 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Rei Miyasaka9-Apr-07 17:38
Rei Miyasaka9-Apr-07 17:38 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Nick Higgs10-Apr-07 9:25
Nick Higgs10-Apr-07 9:25 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Rei Miyasaka10-Apr-07 10:52
Rei Miyasaka10-Apr-07 10:52 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Nick Higgs10-Apr-07 12:43
Nick Higgs10-Apr-07 12:43 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Rei Miyasaka11-Apr-07 2:49
Rei Miyasaka11-Apr-07 2:49 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Nick Higgs12-Apr-07 10:57
Nick Higgs12-Apr-07 10:57 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Rei Miyasaka12-Apr-07 14:16
Rei Miyasaka12-Apr-07 14:16 
First off, please change your tone to be less personal. "Advocating" this and "defending" that makes it sound as if it's a discussion between activists, not developers.


Nick Higgs wrote:
That is where I find the contradiction alluded to in my previous posts; you seem, on the one hand, to be excessively concerned with the separation to application and business logic, while on the other hand, to think that separation of business and data logic should be avoided.


You can have either application and business together with data isolated, or application isolated and business and data tied together. Which is better depends on what you're doing, which I'll explain next, but unfortunately with SQL, you can only have either (not so true with some other technologies such as WCF). There is no contradiction, just a compromise.

Nick Higgs wrote:
In your scheme, if you move to a different database technology, you lose all your business logic and are left with a shell of an application. Moreover, if you wish to run the application out of different databases at different times, you have to reproduce the business logic in each database, making modifying the application harder and more dangerous.


There's quite a difference between writing commercial software and writing custom solutions, the latter of which is the more common scenario for SQL databases. In commercial software, the data providers may be less permanent than the clients (because the clients are packaged in boxes and CDs and sent to the users along with EULAs and quality promises and such), whereas in custom solutions, the opposite is true.

The cost of migrating databases is tremendous regardless of the design of the database. Many companies I've worked for to this day use database technologies that date back to the COBOL days; in all cases, the UIs are obviously updated to run on Win32. At times I've had to guess which tables were safe to touch and which weren't. Needless to say, it was scary.

Nick Higgs wrote:
Furthermore, are you aware that by advocating implementing a business tier as a structureless bag of stored procedures, you're coming close to rejecting, not only the object orientated paradigm, but modularization in code all together? This goes somewhat beyond the usual defense of stored procedure usage, which generally advocates stored procedures as an additional sub-tier between the business and data tiers. If this discussion requires me to defend modularization in programming, then so be it, but I would be surprised if you mean to dismiss it so acutely.


You're not going to go on a crusade in the name of modularization because I'm not even attacking it.

Let's not forget that object oriented programming is a method of abstraction, not an abstraction in itself. What I might call a 4 vertex quad, you might call a 2 triangle plane. What you might call a collection of H2O moleclues I might call a cup of water. Which is better depends on how you use it.

So, is a database a bag of structureless procedures or a bag of procedureless structures? When you model data, you have a purpose for the data, so you want the data to behave purposefully. From a holistic perspective, there's nothing un-modular or un-OOP about considering the whole database and business logic to be an autonomous entity.

A common mindset nowadays, especially in companies such as Microsoft where security has become top priority, is to reduce the "surface area" of an object. By creating stored procedures (or views with INSTEAD OF triggers if you prefer), you are encapsulating the database and creating interfaces which the consumer can know to behave as is described and intended.

Nick Higgs wrote:
Are you really suggesting that the Person class in my article (for example) would be better represented by a set of stored procedures?


Unfortunately, yes. SQL normally exposes raw data with no validation, which as a C# developer I'm sure you know is not a good idea. As ridiculously tedious as it is, this is the better way to do things.

Would I do it for a really quick RAD app? No. Is stored procedures for CRUD queries silly? No. Because really, does Person represent a Person or a Person table?


There are things more important than line count. Sorry.
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
Nick Higgs14-Apr-07 11:13
Nick Higgs14-Apr-07 11:13 
GeneralRe: Stored Procedures "silly" for CRUD operations? Pin
jarle8017-Apr-07 21:03
jarle8017-Apr-07 21:03 
GeneralRe: Stored Procedures &amp;quot;silly&amp;quot; for CRUD operations? [modified] Pin
Nick Higgs19-Apr-07 13:56
Nick Higgs19-Apr-07 13:56 
AnswerRe: Stored Procedures "silly" for CRUD operations? Pin
Bohicette29-Oct-07 11:03
Bohicette29-Oct-07 11:03 
Generalsuggestion Pin
timvw9-Apr-07 5:31
timvw9-Apr-07 5:31 
GeneralRe: suggestion Pin
Nick Higgs9-Apr-07 5:50
Nick Higgs9-Apr-07 5:50 
GeneralRe: suggestion Pin
mr_lasseter9-Apr-07 15:49
mr_lasseter9-Apr-07 15:49 
GeneralRe: suggestion Pin
seesharper9-Apr-07 21:43
seesharper9-Apr-07 21:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.