6
\$\begingroup\$

I'm looking for any comments or feedback on my database access class. Security and speed are two things I'm most concerned about.

One thing to note is this class has to work in a C# .NET 2 environment, so anything that's more modern would be interesting to me, but please note in the title of your answer if the comments/feedback require a newer .NET version.

using Microsoft.Practices.EnterpriseLibrary.Common;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;

/// <summary>
/// This is the base class for database access classes. This is the only
/// class that should directly talk to the database. Every class or page
/// that neads to access the database should be refering to this or a
/// derived class.
/// </summary>
public class DatabaseAccess
{
    static string LastDatabaseName = "";
    static Database database = null;
    static int errorCount = 0;

    /// <summary>
    /// Execute a SQL statement on the default database
    /// </summary>
    /// <param name="SQL">The SQL statement to execute</param>
    /// <returns>DataTable of selected results</returns>
    public static DataTable ExecSQL(string SQL)
    {
        List<SqlParameter> Parameters = new List<SqlParameter>();
        return ExecSQL("", SQL, Parameters);
    }
    /// <summary>
    /// Execute a SQL statement on the default database
    /// </summary>
    /// <param name="SQL">The SQL statement to execute</param>
    /// <param name="Parameters">The parameters for the SQL statement</param>
    /// <returns>DataTable of selected results</returns>
    public static DataTable ExecSQL(string SQL, List<SqlParameter> Parameters)
    {
        return ExecSQL("", SQL, Parameters);
    }
    /// <summary>
    /// Execute a SQL statement on the requested database
    /// </summary>
    /// <param name="DatabaseName">The database to execute the SQL on</param>
    /// <param name="SQL">The SQL statement to execute</param>
    /// <returns>DataTable of selected results</returns>
    public static DataTable ExecSQL(string DatabaseName, string SQL)
    {
        List<SqlParameter> Parameters = new List<SqlParameter>();
        return ExecSQL(DatabaseName, SQL, Parameters);
    }
    /// <summary>
    /// Execute a SQL statement on the requested database
    /// </summary>
    /// <param name="DatabaseName">The database to execute the SQL on</param>
    /// <param name="SQL">The SQL statement to execute</param>
    /// <param name="Parameters">The parameters for the SQL statement</param>
    /// <returns>DataTable of selected results</returns>
    public static DataTable ExecSQL(string DatabaseName, string SQL, List<SqlParameter> Parameters)
    {
        // Database access variables
        // Database database = null;
        DbCommand command = null;
        DataTable table = new DataTable();

        if (DatabaseName != LastDatabaseName || database == null)
        {
            if (database != null)
                database = null;

            if (DatabaseName != "")
                database = DatabaseFactory.CreateDatabase(DatabaseName);
            else
                database = DatabaseFactory.CreateDatabase();
        }
        LastDatabaseName = DatabaseName;

        command = database.GetSqlStringCommand(SQL);
        foreach (SqlParameter p in Parameters)
        {
            database.AddInParameter(command, p.ParameterName, p.DbType, p.Value);
        }

        try
        {
            if (!SQL.StartsWith("UPDATE") && !SQL.StartsWith("DELETE"))
                table = database.ExecuteDataSet(command).Tables[0];
            else
                database.ExecuteNonQuery(command);

            errorCount = 0;
        }
        catch (SystemException e)
        {
            errorCount++;

            if (errorCount < 2)
            {
                CMSLog.Exception(e);
                CMSLog.Info(SQL);
                CMSUtil.setSession("Exception", e.Message);
                CMSUtil.setSession("ExceptionExtra", e.StackTrace);
                HttpContext.Current.Response.Redirect("~/CMS/SiteError.aspx");
            }
            else
            {
                HttpContext.Current.AddError(new Exception("Looping DB Error: " + e.Message));
            }
        }

        return table;
    }
}

A simple example using the class:

string strValue = "Some Untrusted Value";
List<SqlParameter> parms = new List<SqlParameter>();
parms.Add(new SqlParameter("Value", strValue));

string sql = "SELECT * FROM TableName WHERE FieldName=@Value";
DataTable tblResults = DatabaseAccess.ExecSQL(sql, parms);
\$\endgroup\$
1
  • \$\begingroup\$ In your summary, "neads" should be "needs" and "refering" should be "referring". ;p \$\endgroup\$ – Steven Jeuris May 24 '11 at 9:40
6
\$\begingroup\$

A few quick ideas:

  • Check spelling.
  • Use string.Empty instead of "" for improved readability and performance.
  • Always use visibility modifiers - For example your fields lack the typical "private" keyword. Example private static Database database = null;
  • Re-evaluate your design choice to go with a static class. Static classes are know for causing head-aches such as threading problems. Read more here to start with if you are unsure. Just removing all "static" keywords will make the class just as usable.
  • Use lower case for local variables and parameters. For example: "var parameters = new List();"
  • As for the error-counting-logic I don't even know where to start... :-/ Perhaps the whole thing can be done in some other way.
  • Consider the naming of ExecSQL - SQL commands can be both inserts and selects and also other types of commands, while this class concerns itself with select
  • Consider using IEnumerable<T> instead of List<T> since you're only iterating the List<T>.

If you use c# 3.0 or later

  • Use the var keyword if target type is redundant. Example List<SqlParameter> Parameters = new List<SqlParameter>();
\$\endgroup\$
3
  • \$\begingroup\$ Hi and thanks. To explain the error counting logic (I'll add comments to the code as well). The CMSLog class logs information into the database and itself uses the DatabaseAccess class. The counting catches errors network/connectivity errors, which would result in an endless loop, and breaks the loop with an error page. The idea is all possible errors are logged without stopping the application. \$\endgroup\$ – Justin808 May 24 '11 at 16:51
  • \$\begingroup\$ @Justin808 np. yeah I got it. It is just... done in a very strange way for me. \$\endgroup\$ – vidstige May 24 '11 at 19:10
  • \$\begingroup\$ Since .NET 2.0 string.Empty performs no better than "". Still worth using for readability, though. stackoverflow.com/a/151481/3668251 \$\endgroup\$ – user50167 Sep 27 '19 at 15:56
3
\$\begingroup\$

1)

    if (DatabaseName != LastDatabaseName || database == null)
    {
        if (database != null)
            database = null;

        if (DatabaseName != "")
            database = DatabaseFactory.CreateDatabase(DatabaseName);
        else
            database = DatabaseFactory.CreateDatabase();
    }

First two lines inside your if do not make any sense. Anyway you're assigning another value to database variable below. I would write it as:

    if (DatabaseName != LastDatabaseName || database == null)
    {
        database = DatabaseName != "" ?
                   DatabaseFactory.CreateDatabase(DatabaseName) :
                   DatabaseFactory.CreateDatabase();
    }

2) Define your variables closer to the first assignment place. command variable is defined 10 lines of code before it is assigned and also has some value which is not used at all.

3) Looks like you're following One return rule. I personally do not think this rule should be followed (at least in C#). For example you're assigning dataTable variable in case of select and do nothing more with, only returning it. But I (as maintainer of your code) see you're assigning it and I have to read the method till the end. Just return dataTable right there - this will let me know that nothing done with it later - it will save my time.

4) In case of insert or update statements you're returning empty dataTable. I would return either null to distinguish it from select statement or dataTable with one cell which will contain number of updated entries (at the moment you're swallowing this information).

5) The entire code is not that intuitive which means that it is not easy readable which means that it is not easy maintainable. I would never guess that in order to add parameter to the command I should look for a method in database class.

6) I would never expect that such method will create databases. This is not intuitive at all.

\$\endgroup\$
2
  • \$\begingroup\$ JIM-compiler - Thanks for the answer. The 'if (database != null) { database = null; }` bit will close an exiting connection if its open and clear out the variable so a new connection can be opened. It prevents hanging connections to the database. \$\endgroup\$ – Justin808 May 24 '11 at 16:54
  • \$\begingroup\$ The class will never create a database unless you pass the SQL to ExecSQL to do so. The DatabaseFactory.CreateDatabase method is part of Microsoft.Practices.EnterpriseLibrary and creates a database object to use based on a named connection string in the web.config. Not passing a name to the method uses the defined default connection string in web.config. \$\endgroup\$ – Justin808 May 24 '11 at 16:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.