Click here to Skip to main content
16,008,010 members
Articles / Desktop Programming / MFC
Article

An OO Persistence Approach using Reflection and Attributes in C#

Rate me:
Please Sign up or sign in to vote.
4.19/5 (39 votes)
9 Jun 20032 min read 113.8K   957   49   13
Describes how to use C# attributes , ADO.NET and C# to persist objects to databases.

Introduction

Being a J2EE developer in the past and a very big fan of OOD , I started a few months ago to work in .NET. Now I have seen the .NET data persistence approach, the DataSets, the DataReaders and DataAdapters in ADO.NET, which I consider very powerful when working with more that one record in a table, but I still like using Business Objects in my design, so I wrote a small persistence framework for .NET.

Using JDO in the past , I considered making a XML description for each BO to map it to the database tables, but I saw that a better thing would be to use .NET attributes. I have found an article on the Internet with an example of using attributes for such a purpose.

Let’s say we have a BO called User:

C#
public class User
{
   public User(){}
   private long _id;
   private string _name;
   private string _username;
   private string _password;

   //for each of these fields we have a Property
   public long UserID {
      set{_id = value;}
      get {return _id;}
   }

   //etc 
   …     
}

Now let’s map in to the database:

C#
[DBTable("Users","dbo")]
[Serializable]
public class User
{
    public User(){}
    private long _id;
    private string _name;
    private string _username;
    private string _password;

    [DBPrimaryKeyField("ID_User",DbType.Int16)]
    [DBColumn("ID_User",DbType.Int16,false)]
    public long UserID 
    {
        set{_id = value;}
        get {return _id;}
    }

    [DBColumn("Name",DbType.String,true)]
    public string Name
    {
        //set, get
    }

    [DBColumn("Username",DbType.String,false)]
    public string Username
    {
        //set, get
    }

    [DBColumn("Password",DbType.String,false)]
    public string Password
    {
        //set, get
    }
}

We have 3 attribute classes DBTable, DBPrimaryKey, DBColumn. Let’s explain what each does.

  • DBTable(string tableName, string databaseOwner)

    maps the business object to a database table

  • DBPrimaryKey(string pkFieldName, DBType pkFieldType, bool isAutoIncrement)

    maps the Primary column to a BO property

  • DBColumn(string tableFieldName, DBType fieldType, bool allowsNulls)

    maps a table column to a BO property.

Now let’s look at some things that can be made with the BO. This is done using an IPersistenceService interface.

C#
public interface IPersistenceService 
{
    object Create(Object o, Type objectType);
    void Update(Object o, Type objectType);
    void Delete(Object id, Type objectType);
    int Count(string condition, Type objectType);
    Object GetObject(Object id, Type objectType);
    void LoadDataSet(DataSet dataSet, String sqlQuery);
    void LoadDataSet(DataSet dataSet, String sqlQuery, String tableName);
    object Create(SqlConnection con, Object o, Type objectType);
    void Update(SqlConnection con,Object o, Type objectType);
    void Delete(SqlConnection con,Object id, Type objectType);
    int Count(SqlConnection con,string condition, Type objectType);
    Object GetObject(SqlConnection con,Object id, Type objectType);
    void LoadDataSet(SqlConnection con,DataSet dataSet, String sqlQuery);
    void LoadDataSet(SqlConnection con,DataSet dataSet, String sqlQuery,
                     String tableName);
    SqlConnection GetConnection();
    void CloseConnection(SqlConnection con);
}

We have a factory for this interface to get an implementation. In this case the implementation is made against SQL Server in PersistenceServiceImpl. We get an implementation:

C#
String connectionString = "…";
IPersistenceService ps = PersistenceServiceFactory.getPS(connectionString);

Now let’s create an User and persist it.

C#
User u = new User();
u.Name = "Dan Bunea";
u.Password = "myusername";
u.Password = "mypassword";
//obtain id for the new user in the 
long id = (long)ps.Create(u,typeof(User));

or just: (if you don’t need the ID obtained for the object)

C#
ps.Create(u,typeof(User));

Now that we have a user in the Users table , lets retrieve it to see how that’s done.

C#
User U2 = (User)ps.GetObject(id,typeof(User));

Let’s update it and save the changes.

C#
U2.Name = "Dan Bunea Updates";
U2.Password = "newusername";
U2.Password = "newpassword";
P2s.Update(U2,typeof(User));

Simple J , no? Now let’s delete it.

C#
Ps.Delete(id,typeof(User));

l have also included some methods to load DataSet objects easily, but also if you have more operations that need to use the same connection. Let’s say we want to make all these operations on one connection:

C#
SqlConnection con = ps.GetConnection();
User u = new User();
u.Name = "Dan Bunea";
u.Password = "myusername";
u.Password = "mypassword";
object id = (long)ps.Create(con,u,typeof(User));
User U2 = (User)ps.GetObject(con,id,typeof(User));
U2.Name = "Dan Bunea Updates";
U2.Password = "newusername";
U2.Password = "newpassword";
P2s.Update(con,U2,typeof(User));
Ps.Delete(con,id,typeof(User));
ps.CloseConnection(con);

Let’s talk a little about performance. For each BO the user must not be concerned about INSERT, UPDATE, DELETE and SELECT (for pk) queries. They are automatically generated by the code using the attributes specified in the object. Each statement for each BO is only generated once, and then cached with it’s parameters. The next time the user performs the same operation, the query is taken from cache, also its parameters and using reflection each parameter is given a value, the query is executed.

With special thanks for those that have written other articles that helped me write this, and in the hope that this was a pleasant and useful reading , I finish my small article here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAbout System.Type Pin
John Gunnarsson20-Jun-03 3:01
John Gunnarsson20-Jun-03 3:01 
GeneralSome questions... (of who is wanting to understand some thing; -) Pin
Mikeboo11-Jun-03 9:53
Mikeboo11-Jun-03 9:53 
GeneralRe: Some questions... (of who is wanting to understand some thing; -) Pin
danbunea11-Jun-03 21:57
danbunea11-Jun-03 21:57 
GeneralNice idea but... Pin
worldspawn10-Jun-03 20:38
worldspawn10-Jun-03 20:38 
GeneralRe: Nice idea but... Pin
Dan Bunea10-Jun-03 21:36
Dan Bunea10-Jun-03 21:36 
GeneralRe: Nice idea but... Pin
worldspawn10-Jun-03 22:03
worldspawn10-Jun-03 22:03 
GeneralRe: Nice idea but... Pin
danbunea10-Jun-03 22:19
danbunea10-Jun-03 22:19 
GeneralRe: Nice idea but... Pin
Member 5368317-Jun-03 5:11
Member 5368317-Jun-03 5:11 
I don't think you can say "relational concepts don't exist in OO". Certainly the idea of using a 2 (or 3) column table to store the relation is not an OO concept but an RDBMS implementation of the concept of a relation. But e.g the UML has relations; in fact inheritance is modeled in the UML as a relation, and inheritance is definitely an OO concept!

You have to think of a more general model where relations can be 2 or 3 way, where they can be navigated in both directions, and where the relations offer a whole-part relationship (aggregation and composition) where creating/deleting one BO might automatically create/delete others. These can all be modeled with attributes but it would need some work, especially if the BOs built up have cyclical references. For example if Role had a Users[] property.

For three-way relations you can't simply have a collection of references in your object, you do still need an intermiate object that represents both of the far ends of the relationship. For example, consider that a user may have several roles, and for each role there may be some information about when that role was allocated, who allocated it, when it finishes, etc etc. This information pertains to the user-role relationship itself, it is not a function of the user or the role independently. User would need a special collection of (role, info) pairs-- an IPairCollection, if you will-- and Role would need a collection of (user, info) pairs. Alternatively there would need to be an Info class that had links to both the User and Role and User would have a collection of Info objects and so would Role, so for User to navigate to Role it would have to go via the Info object.

For myself I would also consider loading relations on demand rather than when the BO itself is loaded, i.e. do it when the Roles property is accessed, not before.

Moreover, I would completely cut out writing the BOs in C# in the first place. I'd rather design the BO classes in the UML (e.g. using Rational Rose), dump them out as an XML description (i.e. XMI) and then process this to automatically generate the .NET classes (either as C# source or using CodeDOM etc, depending on which was more applicable) with the appropriate attributes.
GeneralRe: Nice idea but... Pin
Anonymous18-Jun-03 16:03
Anonymous18-Jun-03 16:03 
GeneralRe: Nice idea but... Pin
Member 5368318-Jun-03 21:40
Member 5368318-Jun-03 21:40 
GeneralRe: Nice idea but... Prevelance Pin
rj4516-Jan-04 18:24
rj4516-Jan-04 18:24 
Generallink don't work Pin
pls10-Jun-03 17:28
pls10-Jun-03 17:28 
GeneralRe: link don't work Pin
Dan Bunea10-Jun-03 21:32
Dan Bunea10-Jun-03 21:32 

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.