Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / SQL

Optimistic Concurrency with C# using the IOC and DI Design Patterns

4.23/5 (6 votes)
25 Feb 2009CPOL2 min read 39K   223  
Discussion of concurrency using the IOC and DI Design Patterns with the PostgreSQL database.

Introduction

My last article showed Optimistic Concurrency using the PostgreSQL database. I wanted to write a C# program with the Design pattern IOC and DI so we can support multiple databases. This version also uses parameterized queries and transactions.

Background

The IOC design pattern written by Shivprasad Koirala can be read in detail at Design Pattern IOC and DI.

Using the Code

Just as described in Shivprasad Koirala's article, I have an abstract class iDatabase that the actual database classes derive from. I have implemented Postgres only, the other could be SQLServer or any other database.

classIOCDI.gif

In the abstract class, I have set the methods that need to be implemented in the derived classes. There is some code in this class since it is an abstract class and not an interface.

C#
public abstract class iDatabase
{
    string strdbName;        // the database name
    string strServer;        // the database name
    string struserName;      // the user name
    string strpassword;      // the password
    string strSAName;        // the SA user name
    string strSApassword;    // the SA password

    public iDatabase()
    {
        strdbName = "";             // the database name
        strServer = "";             // the database name
        struserName = "";           // the user name
        strpassword = "";           // the password
        strSAName = "";             // the SA user name
        strSApassword = "";         // the SA password
    }

    public abstract void CheckDBConnectionInfo();

    public abstract string GetUserPassword(string userName);

    public abstract int UpdateUser(UserData ud);

    public abstract int AddUser(string userName, string Password, string ModUser);

    public abstract UserData GetUser(string userName);
}

The update user code in the postgres class is a normal piece of code as you would expect. It is this code that runs and updates the user table if PostreSQL is selected. The main difference is this code does handle Optimistic Concurrency and sends back the entire data class for the database record instead of each data item.

C#
public override int UpdateUser(UserData ud)
{
    string sqlstring;
    int iret = -1;
    bool errflg = false;
    Object result;
    //
    // get the connection string
    //
    sqlstring = GetConnectionString();
    NpgsqlConnection conn = new NpgsqlConnection(sqlstring);
    conn.Open();
    // create a transaction object
    NpgsqlTransaction trano = conn.BeginTransaction();
    // set the sql command
    //                          uuid,  int,   str,   str,   str
    sqlstring = "Update_MinUser(:Arg1, :Arg2, :Arg3, :Arg4, :Arg5)";
    NpgsqlCommand command = new NpgsqlCommand(sqlstring, conn, trano);
    command.CommandType = CommandType.StoredProcedure;

    try
    {
    // Now add the parameter to the parameter collection of the command
    // specifying its type.
    command.Parameters.Add(new NpgsqlParameter("Arg1", DbType.Guid));

    // Now, add a value to it and later execute the command as usual.
    command.Parameters[0].Value = ud.UserId;

    // Now add the parameter to the parameter collection of the command
    // specifying its type.
    command.Parameters.Add(new NpgsqlParameter("Arg2", DbType.Int16));

    // Now, add a value to it and later execute the command as usual.
    command.Parameters[1].Value = ud.UserConcur;

    // Now add the parameter to the parameter collection of the command
    // specifying its type.
    command.Parameters.Add(new NpgsqlParameter("Arg3", DbType.String));

    // Now, add a value to it and later execute the command as usual.
    command.Parameters[2].Value = ud.UserName;

    // Now add the parameter to the parameter collection of the command
    // specifying its type.
    command.Parameters.Add(new NpgsqlParameter("Arg4", DbType.String));

    // Now, add a value to it and later execute the command as usual.
    command.Parameters[3].Value = ud.UserPassword;

    // Now add the parameter to the parameter collection of the command
    // specifying its type.
    command.Parameters.Add(new NpgsqlParameter("Arg5", DbType.String));

    // Now, add a value to it and later execute the command as usual.
    command.Parameters[4].Value = ud.ModUser;
    }
    catch (NpgsqlException nex)
    {
        trano.Rollback();
        throw new Exception(nex.Message);
    }

    try
    {
        result = command.ExecuteScalar();
    }
    catch (NpgsqlException nex)
    {
        trano.Rollback();
        throw new Exception(nex.Message);
    }
    finally
    {
        if (!errflg)
        {
            trano.Commit();
        }
        conn.Close();
    }
    iret = (int)result;
    return (iret);
}

Where I choose the type of database is in the clsUser class, as shown below. If I wanted to change the type of database at runtime I would have checked a setting somewhere and done an if statement to create the required class.

C#
public ClsUser()
{
    idb = new Postgres();
    idb.DBName = "CONNC";
    idb.Server = "localhost";
    idb.SAUserName = "postgres";
    idb.SAPassword = "Password1";
}

Using the code is extremely easy and clean. In this program I added a user in case it is not already added. I then get the user data record. Then I update the password and call the update procedure. Since I still have the original data, I call the update procedure again to see what will happen. As you would expect, I get an error since the record has already been updated.

C#
UserData    ud1;
int ires;

ClsUser ccus;
ccus = new ClsUser();

// do a try catch since we may have run this once before
try
{
    ires = ccus.AddUser("User1", "User1p", "Don");
}
catch (Exception ex)
{
    MessageBox.Show("Error from {0}" + ex.Message, "test");
}

// get the user data
ud1 = ccus.GetUser("User1");
// change the password.
ud1.UserPassword = "NewPassword";
// update the data.
ires = ccus.UpdateUser(ud1);
if (ires != 0)
{
    MessageBox.Show("Error updating the user record.");
}

// now with the original data try and update it again. It should give us an error.
// this will happen since the Optimistic Concurrency has already been updated.

// change the password.
ud1.UserPassword = "NewPassword1";
// update the data.
ires = ccus.UpdateUser(ud1);
if (ires != 0)
{
    MessageBox.Show("Error updating the user record the second time.");
}

Points of Interest

One item to take from this design pattern is it is a lot of work and code to support the possibility of using two or more databases. If you know you are not going to support other databases, there are other patterns that might be better. The file Db1.sql in the zip file is the SQL file necessary to create the tables for this project. As part of responding to user input, I modified the code to support parameterized queries and transactions. In doing this, I found that Npgsql version 1 did not support UUIDs all that well. I had to download the newer version 2.

History

  • 9th February, 2009 - Initial release
  • 23rd February, 2009 - Updated parameterized queries, transactions

License

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