Click here to Skip to main content
11,647,170 members (70,500 online)
Click here to Skip to main content

Very Lightweight Data Access Layer in C# and .NET 2.0

, 4 Apr 2007 CPOL 147.5K 2.3K 125
Rate this:
Please Sign up or sign in to vote.
Very Lightweight Data Access Layer in C# and .NET 2.0

Introduction

In my spare time I write a lot of small applications for my friends and family. The common thing for all these small applications is that they grab some data from the database, display the data to the user and save the changes back to database.

When I design programs, I break them into tiers. Most of the time I have three logical tiers, which are User Interface Layer, Business Logic and Data Access Layer. I usually start with the class diagram, and only after I am happy with the class diagram then I build the tables and stored procedures to access the data from/to tables. User Interface is usually dictated by the user.

As I was building more and more of these small apps, I realized that I was spending a lot of time on my Data Access Layer, since I had to create mappings for populating the business objects from the tables in database, and since each application had a different business logic and business objects, I ended up writing Data Access Layer from scratch. So to make my life easier, I decided to build a generic data access helper class that could be reused on all my projects with little or no changes.

To be able to populate the object with the data from the database, the business object needs to have public properties with GET and SET methods. Then using reflection I could query the object for the public properties and if the property name matched the name of the field in the table, then the object would be populated with the data from that field.

There were times when the properties and the fields in the database were different or that the object could have more properties than the fields in database, so I decided to have two ways of populating the object with the data from the database:

  • Use a mapping class that would provide information of what property is mapped to what field, and
  • Decorate the properties with a custom attribute to show to what field the property is mapped.

The first step is to build a mapping class. This is a very simple class that holds a collection of a strings. The first string would be in the format property=field, where property is the name of the property of the object, and the field is the name of the field in database. So the mapping class, after implementation, looks like this:

/// <summary>
/// This class holds information about mapping a database field to a 
/// object property.
/// </summary>
public class Mapper
{
    private List<string> mMappingInfo;

    /// <summary>
    /// Default constructor
    /// </summary>
    public Mapper()
    {
        mMappingInfo = new List<string>();
    }


    /// <summary>
    /// Add mapping information. This method can be used to add more than 
    /// one mapping at a time.
    /// You could use it like: mapper.Add("property1=field1", 
    ///     "property2=field2", "property3=field3", ...)
    /// </summary>
    /// <param name="mappings">mapping information in format 
    //     "[property name]=[field name]"</param>
    public void Add(params string[] mappings)
    {
        foreach (string map in mappings)
            mMappingInfo.Add(map);
    }


    /// <summary>
    /// Return mapping information held in this class as string array
    /// </summary>
    public string[] MappingInformation
    {
        get
        {
            string[] mappings = new string[mMappingInfo.Count];
            mMappingInfo.CopyTo(mappings);

            return mappings;
        }
    }


    /// <summary>
    /// Indexer property. By providing the name it returns the mapping info
    /// for that property.
    /// If the mapping information for the provided property does not exist,
    /// the indexer 
    /// return null. 
    /// You could use it like: string mapInfo = mapper["property1"];
    /// </summary>
    /// <param name="propertyName">the name of the property to 

return 
    /// mapping information</param>
    /// <returns>mapping information for the property 

provided</returns>

The next thing to implement is the custom attribute that will be used to map properties with the fields in database. Again this is a very simple attribute that will be used to store the name of the field. The implementation looks like:

/// <summary>
/// Specifies the name of the field in the table that the property maps to 
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
    public class DBFieldAttribute : Attribute
    {
        private string mFieldName;

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="fieldName">name of the field that the 

property will
        /// be mapped to</param>
        public DBFieldAttribute(string fieldName)
        {
            mFieldName = fieldName;
        }

        public string FieldName
        {
            get { return mFieldName; }
        }
    }

Since this attribute can only be used on properties, the class is decorated with AttributeUsage(AttributeTargets.Property) attribute.

And now finally the only thing left is the helper class that will be used to retrieve data from the database. The implemented class looks like:

public class DBHelper
{
    /// <summary>
    /// Generic method. Gets an object of type T from the data reader. It 
    /// uses mapping information provided to read a field from the reader, 
    /// and gets the property name and sets the value of the property with 
    /// the data which are held in database field
    /// </summary>
    /// <typeparam name="T>The type of object to be 

instantiated</typeparam>

DBHelper is a static class which means that it does not need to be instantiated and all the methods are static as well. It is generic in a sense that you provide the type of the object to be loaded from the database, and the class creates the objects and populates its public properties (the ones that either are decorated with the DBField attribute or mapping information is provided) from the data read from the table.

As you can see from the code above we are using constraints on generics, which basically means that the generic type T has to be an object (reference type) and can't be of value type (primitives like int, float, byte, and so on, or struct which is also of value type). The above class also uses reflection to check if the properties have the DBField set, and if yes then the code reads the attribute, gets the field name from the attribute and reads the data from the table.

Using the code

To use the class above is very easy, as you can see from the following example. Suppose we have a table that contains persons' details and its definition is like the below:

and a class Person that looks like the code below:

public class Person
{
    private int mID;
    private string mName;
    private string mSurname;
    private DateTime mDob;
    private string mProfession;

    public Person()
    {
    }


    [DBField("ID")]
    public int ID
    {
        get { return mID; }
        set { mID = value; }
    }

    [DBField("Name")]
    public string Name
    {
        get { return mName; }
        set { mName = value; }
    }

    [DBField("Surname")]
    public string Surname
    {
        get { return mSurname; }
        set { mSurname = value; }
    }

    [DBField("DOB")]
    public DateTime DateOfBirth
    {
        get { return mDob; }
        set { mDob = value; }
    }

    [DBField("Profession")]
    public string Profession
    {
        get { return mProfession; }
        set { mProfession = value; }
    }


    public int CalculateAge()
    {
        int age = DateTime.Now.Year - mDob.Year;
        return age;
    }
}

As you can see from the code of class Person, public properties are decorated with the attribute DBField. Each attribute corresponds with the field name in the table. Now to read the data from the table and return populated objects we would use the DBHelper class like this:

public List<Person> GetAllPersonsFromDB()
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        SqlCommand command = new SqlCommand("Select * from Persons order 

by 
            _Surname", connection);
        connection.Open();
        List<Person> persons = 

DBHelper.ReadCollection<Person>(command);
        return persons;
    }
}

or, if you need to retrieve a particular object, you could use the code below:

public Person GetPersonByID(int id)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        SqlCommand command = new SqlCommand("Select * from Persons where 

ID 
            _= @ID", connection);
        SqlParameter param = command.Parameters.Add("@ID", 

SqlDbType.Int);
        param.Value = id;

        connection.Open();
        Person person = DBHelper.ReadObject<Person>(command);
        return person;
    }
}

If for some reason you need to get data from another table where the fields are named differently from the attributes, we need to provide mapping information by using Mapper class to the DBHelper.

Example: If we need to read data from another table and the table definition is as follows:

To read the data from the above table, we need to provide mapping information to DBHelper as follows:

public List<Person> GetAllPersonsFromDB()
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        SqlCommand command = new SqlCommand("Select * from Persons order 

by 
            _Surname", connection);
        Mapper mapper = new Mapper();
        
        // Provide the mapping information in format: "[Property Name]=
        // [Field Name]" for the appropriate fields 
        mapper.Add("ID=ID", "Name=FirstName", 

"Surname=Surname", 
            _"Profession=Profession", 

"DateOfBirth=DateOfBirth");

        connection.Open();
        List<Person> persons = 

DBHelper.ReadCollection<Person>(command, 
            _mapper);
        return persons;
    }
}

As you can see from the examples above, the DBHelper class can be used easily on different projects to get the data from the database and convert them to objects in a convenient way. So the first step is to declare the appropriate class with default constructor (parameterless constructor) and provide public properties with get/set methods, then either use DBField attribute on properties to map them to database fields, or use Mapper class to provide mapping information.

This class could be extended to support storing object to the database as well.

History

I have changed the source code, so now the DBHelper class can be used to insert/update business objects to database. I have also created a small demo program (attached to source solution).

License

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

Share

About the Author

Fitim Skenderi
Software Developer (Senior) KOS-LIGHTS.COM
Sweden Sweden
No Biography provided

You may also be interested in...

Comments and Discussions

 
BugPossible Memory Leak in public static List<T> ReadCollection<T>(IDbCommand command) where T function Pin
neil_mahaseth4-Mar-15 3:41
memberneil_mahaseth4-Mar-15 3:41 
GeneralRe: Possible Memory Leak in public static List<T> ReadCollection<T>(IDbCommand command) where T function Pin
Fitim Skenderi5-Mar-15 21:37
memberFitim Skenderi5-Mar-15 21:37 
GeneralMy vote of 5 Pin
hari111r28-Nov-12 16:46
memberhari111r28-Nov-12 16:46 
GeneralMy vote of 5 Pin
Kanasz Robert25-Sep-12 22:40
mvpKanasz Robert25-Sep-12 22:40 
QuestionMapper Class - This Functions Pin
Jay D Morlan18-Jul-12 7:56
memberJay D Morlan18-Jul-12 7:56 
AnswerRe: Mapper Class - This Functions Pin
Jay D Morlan18-Jul-12 9:26
memberJay D Morlan18-Jul-12 9:26 
QuestionReally Article Pin
Muhammed Yaseen21-Nov-11 21:56
memberMuhammed Yaseen21-Nov-11 21:56 
GeneralMy vote of 1 Pin
Dino77714-Nov-11 3:19
memberDino77714-Nov-11 3:19 
GeneralGet the last ID of an inserted record Pin
obinna_eke2-Nov-10 6:33
memberobinna_eke2-Nov-10 6:33 
GeneralRe: Get the last ID of an inserted record Pin
obinna_eke2-Nov-10 21:35
memberobinna_eke2-Nov-10 21:35 
GeneralNested Object Pin
sky391317-Sep-10 23:44
membersky391317-Sep-10 23:44 
GeneralRe: Nested Object Pin
Fabrizio Magosso18-Oct-10 23:41
memberFabrizio Magosso18-Oct-10 23:41 
GeneralMy vote of 5 Pin
pp_williams7-Jul-10 14:42
memberpp_williams7-Jul-10 14:42 
GeneralOMG! Pin
NandoMan3-Jun-10 11:32
memberNandoMan3-Jun-10 11:32 
GeneralRe: OMG! Pin
NandoMan3-Jun-10 11:38
memberNandoMan3-Jun-10 11:38 
GeneralIsDirty on object Pin
Nic_Roche18-Aug-09 10:35
memberNic_Roche18-Aug-09 10:35 
GeneralGenerics Pin
Member 460984425-Mar-09 18:28
memberMember 460984425-Mar-09 18:28 
GeneralRe: Generics Pin
Fitim Skenderi25-Mar-09 23:28
memberFitim Skenderi25-Mar-09 23:28 
GeneralWorking with Stored Procedure Output Parameters Pin
bcox13-Mar-09 2:17
memberbcox13-Mar-09 2:17 
GeneralRe: Working with Stored Procedure Output Parameters Pin
Fitim Skenderi25-Mar-09 23:25
memberFitim Skenderi25-Mar-09 23:25 
GeneralEnhancements/extensions to this framework Pin
technicaltitch8-Oct-08 1:42
membertechnicaltitch8-Oct-08 1:42 
Generallicense Pin
MonsurAhmed31-Jul-08 17:34
memberMonsurAhmed31-Jul-08 17:34 
GeneralRe: license Pin
Fitim Skenderi31-Jul-08 22:52
memberFitim Skenderi31-Jul-08 22:52 
Generalmy wrapper Pin
Marcos Vazquez28-Feb-08 5:40
memberMarcos Vazquez28-Feb-08 5:40 
GeneralRe: my wrapper Pin
Fitim Skenderi31-Jul-08 22:57
memberFitim Skenderi31-Jul-08 22:57 
GeneralRe: my wrapper Pin
Marcos Vazquez1-Aug-08 4:12
memberMarcos Vazquez1-Aug-08 4:12 
GeneralPlease clear my point Pin
Gurvinder Gurvinder19-Jul-07 2:50
memberGurvinder Gurvinder19-Jul-07 2:50 
GeneralRe: Please clear my point Pin
Fitim Skenderi19-Jul-07 3:54
memberFitim Skenderi19-Jul-07 3:54 
GeneralRe: Please clear my point Pin
Gurvinder Gurvinder19-Jul-07 4:11
memberGurvinder Gurvinder19-Jul-07 4:11 
GeneralRe: Please clear my point Pin
Fitim Skenderi23-Jul-07 0:40
memberFitim Skenderi23-Jul-07 0:40 
QuestionDataBinding question Pin
aikipoodle20-May-07 21:25
memberaikipoodle20-May-07 21:25 
AnswerRe: DataBinding question Pin
Fitim Skenderi20-May-07 22:12
memberFitim Skenderi20-May-07 22:12 
GeneralRe: DataBinding question Pin
aikipoodle21-May-07 5:34
memberaikipoodle21-May-07 5:34 
GeneralRe: DataBinding question Pin
Fitim Skenderi21-May-07 10:18
memberFitim Skenderi21-May-07 10:18 
GeneralRe: DataBinding question Pin
aikipoodle21-May-07 11:06
memberaikipoodle21-May-07 11:06 
GeneralRe: DataBinding question Pin
Fitim Skenderi23-May-07 1:06
memberFitim Skenderi23-May-07 1:06 
GeneralRe: DataBinding question Pin
Fitim Skenderi23-May-07 1:28
memberFitim Skenderi23-May-07 1:28 
GeneralDictionary Serialised to XML for Mappings Pin
Anthony Bouch15-Apr-07 14:13
memberAnthony Bouch15-Apr-07 14:13 
GeneralRe: Dictionary Serialised to XML for Mappings Pin
Fitim Skenderi16-Apr-07 5:38
memberFitim Skenderi16-Apr-07 5:38 
QuestionHow do I delete ? Pin
Alex .NET6-Apr-07 2:45
memberAlex .NET6-Apr-07 2:45 
AnswerRe: How do I delete ? Pin
Fitim Skenderi14-Apr-07 3:55
memberFitim Skenderi14-Apr-07 3:55 
QuestionWhy not just use an object database? Pin
Ramon Smits4-Apr-07 12:20
memberRamon Smits4-Apr-07 12:20 
AnswerRe: Why not just use an object database? Pin
Marc Leger4-Apr-07 14:02
memberMarc Leger4-Apr-07 14:02 
GeneralRe: Why not just use an object database? Pin
Ramon Smits4-Apr-07 22:15
memberRamon Smits4-Apr-07 22:15 
GeneralRe: Why not just use an object database? Pin
Craig G. Wilson5-Apr-07 2:53
memberCraig G. Wilson5-Apr-07 2:53 
GeneralNice idea, but.... Pin
C Jones19-Mar-07 0:04
memberC Jones19-Mar-07 0:04 
GeneralRe: Nice idea, but.... Pin
li_robert19-Mar-07 2:42
memberli_robert19-Mar-07 2:42 
GeneralRe: Nice idea, but.... Pin
C Jones19-Mar-07 2:57
memberC Jones19-Mar-07 2:57 
GeneralRe: Nice idea, but.... Pin
Fitim Skenderi19-Mar-07 11:34
memberFitim Skenderi19-Mar-07 11:34 
You have some valid points. However, it depends on the application that you are writting. and the environment that your application will reside. I will try to discuss your points in order.

First point is changing the column names in the database. Well here it depends on your application, and also on the ownership of the database. If you are changing the database schema, then in a sense you are creating a new release (new version), since you need to deploy your new database schema to your customers, which means that your release should include the changes necessary to the application as well as database.

The second point is adding new column to a table. Again, even if you use ORM or any other DAL, you still need to extend the business logic of your application to acommodate the new column. The way I understand it is: since the user requirements have changed which have resulted with a new column added to the table, that means your business object has changed (a new property needs to be added) and as a matter of fact your business logic has changed as well, so you still will need to make the necessary changes to your objects anyway regardless of what methodology/pattern/framework you use.

The third point is the use of stored procedures to overcome the changes to database schema. There are a lot of people that put a lot of business logic in database for performance purposes. I personally think that the database should only be for persisting data, and the stored procedures should only be for CRUD operations to tables, and the business logic should be on the application itself. But again, this is my preference and there are a lot articles and debate of whether the database should be free of any business logic, or if it should contain some business logic.

And the final point is ORM layer with XML/XSD schema. I have two comments here. The first one is, as article suggests this is very lightweight ORM which works for small applications. The second comment is that you can always change/enhance the Mapper class to use XML/XSD schema instead, or event update the DBHelper class as well.
GeneralRe: Nice idea, but.... [modified] Pin
C Jones19-Mar-07 23:35
memberC Jones19-Mar-07 23:35 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150804.2 | Last Updated 4 Apr 2007
Article Copyright 2007 by Fitim Skenderi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid