Click here to Skip to main content
Click here to Skip to main content

Update a database from code

By , 19 Jan 2012
 

Introduction

Keeping a database's structure up to date can be a rather complicated task. If we are maintaining a web server, then it's at first sight a simple task of updating the database when the server is updated. But what to do when the web server is the actual product delivered to customers? Another problem is perhaps more obvious, the product is an application that gets regular updates with bug fixes or new features. This article describes a design to solve this problem.

Using the code

The solution is rather simple, but it requires a great deal of work from the development team. First of all, each release (public as well as internal) must be tagged with a version number. Then each database change must be independent and associated with a particular version number. Each and every database update must be documented with a SQL update script. This is the rule to follow:

It must be possible to gradually update the database, starting with the first create script, and then applying each update script sequentially.

When the application starts, it checks the database's version and compares it with the known latest version. If the database's version is older than the latest version, then the database is updated until its version matches the latest version. Each individual update (e.g., from "1.2" to "1.3") is represented by a class inheriting a base update class. The purpose of the class is to make structural changes to the database (e.g., add a new table or remove a column from an existing table) or update the data (e.g., due to some repaired bug) and finally set the database version.

The base update class DbUpdate contains two virtual methods that shall be inherited by the classes that implements the update SQL scripts. The base class also includes methods to execute the SQL scripts and a method to change the version of the database.

public class DbUpdate
{
    protected List<string> m_UpdateSQLdata = new List<string>();
    protected string m_ConnectionString = string.Empty;
    SqlConnection m_Connection = null;

    public DbUpdate() { }

    virtual public int PerformUpdate()
    {
        try
        {
            m_Connection = new SqlConnection(m_ConnectionString);
            m_Connection.Open();

            foreach (string SQLstr in m_UpdateSQLdata)
            {
                ExecuteSQLStatement(SQLstr);
            }
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine(e.ToString());
            return -1;
        }
        finally
        {
            if (m_Connection != null)
            {
                m_Connection.Close();
            }
        }

        return 0;
    }

    virtual protected void CollectUpdateData()
    {
        throw new NotImplementedException();
    }

    protected void ExecuteSQLStatement(string sqlStr)
    {
        SqlCommand command = new SqlCommand(sqlStr, m_Connection);
        command.ExecuteNonQuery();
    }

    protected void SetNewDatabaseVersion(string version)
    {
        m_UpdateSQLdata.Add(string.Format(
          "update Settings set [value] = '{0}' where [key] = 'DB_VERSION';", 
          version));
    }
}

The update classes inherit the base class and implements the two virtual methods PerformUpdate() and CollectUpdateData().

public class DbUpdate10 : DbUpdate
{
    public DbUpdate10(string connectionString)
    {
        this.m_ConnectionString = connectionString;
    }

    public override int PerformUpdate()
    {
        System.Diagnostics.Debug.WriteLine("Upgrading database 1.0 to 1.1");
        CollectUpdateData();

        // Here you can create a new connection
        // and make changes to the data in the database.
        // E.g. open a dialog to ask for data or re-calculate data.

        return base.PerformUpdate();
    }

    protected override void CollectUpdateData()
    {
        SetNewDatabaseVersion("1.1");
        // 1.
        m_UpdateSQLdata.Add("alter table table_a add test varchar(10);");
    }
}

The class DbUpdateManager performs the update work and can also be used to check if the current database already is up to date. The method DoUpdate() is called once and takes care of the work to update the database to the latest version. The method can update a database from any state and not only from the latest state, i.e., it can update from version 1.1 to 1.6.

public class DbUpdateManager
{
    private DbUpdateResult m_UpdateResult = DbUpdateResult.NotStarted;
    private string m_ConnectionString;

    public DbUpdateManager(string connectionString)
    {
        m_ConnectionString = connectionString;
    }

    public DbUpdateResult DoUpdate()
    {
        try
        {
            m_UpdateResult = DbUpdateResult.NoUpdateNeeded;
            Version currentVersion = GetDatabaseVersion();
            DbUpdate.DbUpdate updateObject = 
              DbUpdateFactory.GetUpdater(currentVersion, m_ConnectionString);
            while (updateObject != null)
            {
                int res = updateObject.PerformUpdate();
                if (res == -1)
                {
                    // Error
                    m_UpdateResult = DbUpdateResult.Error;
                    break;
                }
                Version newVersion = GetDatabaseVersion();
                updateObject = DbUpdateFactory.GetUpdater(newVersion, m_ConnectionString);
                m_UpdateResult = DbUpdateResult.UpdateDone;
            }
            return m_UpdateResult;
        }
        catch
        {
            m_UpdateResult = DbUpdateResult.Error;
            return m_UpdateResult;
        }
    }

    public bool IsUpdateNeeded()
    {
        try
        {
            m_UpdateResult = DbUpdateResult.NoUpdateNeeded;
            Version currentVersion = GetDatabaseVersion();
            DbUpdate.DbUpdate updateObject = 
              DbUpdateFactory.GetUpdater(currentVersion, m_ConnectionString);
            return updateObject != null;
        }
        catch
        {
            m_UpdateResult = DbUpdateResult.Error;
            return false;
        }
    }

    public Version GetDatabaseVersion()
    {
        SqlConnection connection = null;
        try
        {
            connection = new SqlConnection(m_ConnectionString);
            connection.Open();
            SqlDataReader reader = null;
            SqlCommand myCommand = new SqlCommand(
              "select [value] from Settings where [key] = 'DB_VERSION'", 
              connection);
            reader = myCommand.ExecuteReader();
            string version = "0.0";
            while (reader.Read())
            {
                version = reader["value"].ToString();
            }
            reader.Close();
            return new Version(version);
        }
        finally
        {
            if (connection != null)
            {
                connection.Close();
            }
        }
    }
}

The only class that knows which version of the database is the latest is the factory, i.e., DbUpdateFactory. The factory returns a new update class until the version number is no longer matched, meaning that the database is up to date.

public class DbUpdateFactory
{
    public static DbUpdate.DbUpdate GetUpdater(Version version, string connectionString)
    {
        if (version.Equals(new Version(1, 0)))
            return new DbUpdate.DbUpdate10(connectionString);
        else if (version.Equals(new Version(1, 1)))
            return new DbUpdate.DbUpdate11(connectionString);
        return null;
    }
}

History

  • Initial version: 18 January, 2012.

License

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

About the Author

Patrick Ribbing
Architect
Sweden Sweden
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionNot invented here syndromememberGrootjans19 Jan '12 - 5:56 
Why woud you go to the trouble of building something that already exists? There are already tools to manage database migrations. Ruby on Rails has been doing this for a long time. In .net, tools like migrator.net, fluentmigrator and many others do the same.
AnswerRe: Not invented here syndromememberPatrick Ribbing19 Jan '12 - 8:32 
This design is pretty lightweight and is not dependent on any specific language and can easily be ported to the desired language. But in general I think you are right, why spend time and money on something that someone else already has done.
GeneralMy vote of 4memberJohn Brett19 Jan '12 - 3:02 
I think it's useful raising and discussing this area, which is often neglected and can be quite tricky.
However, there are a few issues with the solution shown:
DbUpdateFactory - I find it very clumsy having to hard-code a list of discrete update methods. Surely this can be done automatically from a list of version -> update_script tuples? I've done this myself, pulling the data directly from a .config file (could be any other text source).
It doesn't do anything to help with the really hard work - actually generating and validating the SQL scripts to upgrade one schema to another. I have worked with a system that did this, so I certainly don't underestimate the complexity and database-specific parts of the problem.
Error handling is critical, yet hardly covered. Users won't be happy to have their database left in a half-upgraded state with barely any explanation.
Given that each updater seems to resolve down to a series of lines of text (SQL statements), the design could be a great deal simpler - unless there's a specific need for custom code to be run against specific upgrades.
Oh, and what Pete said.
GeneralRe: My vote of 4memberPatrick Ribbing19 Jan '12 - 8:28 
Thank you for your comments. As you point out, this can be a pretty complex task and you have to be 100% sure about the database structure all the time. If you develop and add a column here, change a column type there and so on you may end up not knowing what the difference with the current stable database is.
 
You are very right about the error handling; I have deliberately left that part out. As we solved it in the project I used this method with, if a database update fails then is the database corrupt and needs to be fixed by the support staff.
QuestionInteresting, but perhaps you could refactor it a little.protectorPete O'Hanlon19 Jan '12 - 2:06 
If I was developing this class, I would start by making DbUpdate abstract. I would then make the method CollectUpdateData abstract as well, so that the dev is forced to implement a body. You could then provide a default implementation of PerformUpdate that looks a bit like this:
    virtual public int PerformUpdate()
    {
        CollectUpdateData();
        try
        {
            m_Connection = new SqlConnection(m_ConnectionString);
            m_Connection.Open();
            foreach (string SQLstr in m_UpdateSQLdata)
            {
                ExecuteSQLStatement(SQLstr);
            }
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine(e.ToString());
            return -1;
        }
        finally
        {
            if (m_Connection != null)
            {
                m_Connection.Close();
            }
        }
        return 0;
    }
In this way, you don't have to provide an override for this method every time. Now, why does this method return an int? Either return a boolean to indicate success or, better still, let the exception bubble up to the next level and catch that. BTW - you shouldn't just check to see if the connection is not null, check to see if it is Open and then Close it. A simpler method would be to wrap this in a using statement, forcing the connection to be disposed of (in all but the most extreme of cases).

Forgive your enemies - it messes with their heads

"Mind bleach! Send me mind bleach!" - Nagy Vilmos

My blog | My articles | MoXAML PowerToys | Mole 2010 - debugging made easier - my favourite utility


AnswerRe: Interesting, but perhaps you could refactor it a little.memberPatrick Ribbing19 Jan '12 - 8:19 
In most cases it would be necessary with using the PerformUpdate() method from the base class. But sometime you need to make changes to data in the database that cannot be done with a SQL script, then you need to override the method, call base.PerformUpdate() and then make changes to the data (e.g. using use input from a dialog).

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 19 Jan 2012
Article Copyright 2012 by Patrick Ribbing
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid