Search code examples
c#asp.netsql-servercoding-styledata-access-layer

Which is the more efficient way to connect to a database?


Having a disagreement with a coworker, and I at this point, I don't care who is right, I'm more curious which is the better solution so that I can use it going forward.

We have different ways to access a system.

Option #1: Creates the database with the code below.

using Microsoft.Practices.EnterpriseLibrary.Data;

namespace Ivans.Healthcare.CommercialAccess.Data
{
    public abstract class DataAccess : DataHelperBase
    {
        public const int commandTimeout = 7200;
        private static Database m_db = null;
        public StringBuilder Status {get; set;}

        public DataAccess()
        {
            this.Status = new StringBuilder();

            if (m_db == null)
            {
                bool bIfRunsOnWebService = false;
                try
                {
                    if (DynamicConfigurationManager.AppSettings["WebService"] != null)
                    {
                        bIfRunsOnWebService = true;
                    }
                }
                catch {}

                if (!bIfRunsOnWebService)
                {
                    m_db = DatabaseFactory.CreateDatabase(DataAccessResource.IDS_DB_ALIAS);
                }
                else
                {
                    m_db = CreateDatabase(DataAccessResource.IDS_WS_DB_ALIAS);
                }
            }
        }

Then everytime a stored procedure needs to be called, the method would contain something like this:

public IEnumerable<InquiryServiceType> GetActive(bool is5010)
{

    Database db = getDB();
    DbCommand dbCmd = db.GetStoredProcCommand(DataAccessResource.IDS_SP_SEL_InquiryServiceTypeData_ListServiceTypes);
    db.AddInParameter(dbCmd, DataAccessResource.IDS_SP_SEL_InquiryServiceTypeData_ListServiceTypes_Is5010Request, DbType.Boolean, is5010);

    DataSet ds = new DataSet();
    db.LoadDataSet(dbCmd, ds, new string[] { DataAccessResource.IDS_TBL_InquiryServiceTypeData });

    return DataSetTranslator.TranslateInquiryServiceTypeDataSet(ds);
}

Option 2

This option is a bit more modular and was trying to create a generic database method.

    private Database currentDB;
private const int commandTimeout = 7200;

public DataAccess(Common.Enums.ConnectionString currentConnection)
{
    currentDB = DatabaseFactory.CreateDatabase(currentConnection.ToDescription());
}

public IEnumerable<T> SelectMany<T>(string spName, params Param[] parameters) where T : IDataPopulate<T>, new()
{
    var storedProcedure = CreateStoredProcedureCommand(spName);
    AddParameters(storedProcedure, parameters);

    IDataReader myReader = null;
    IList<T> listOfItems = new List<T>();

    try
    {
        myReader = currentDB.ExecuteReader(storedProcedure);
        if (myReader == null)
        {
            return listOfItems;
        }

        while (myReader.Read())
        {
            listOfItems.Add(new T().FillObject(myReader));
        }

        return listOfItems;
    }
    catch (Exception ex)
    {
        string message = string.Format("Error Message: {0}\r\nStored Procedure: {1}\r\n", ex.ToString(), spName);
        throw new Exception(message);
    }
    finally
    {
        DataAccessDisposal.DataReader(myReader);
        DataAccessDisposal.StoredProcedure(storedProcedure);
    }
}

Then calling the database would look like this:

public IEnumerable<InquiryServiceTypes> GetAll(int payerID)
{
    Param payerIdParam = new Param("@payerID", DbType.Int32, payerID);
    return dataAccess.SelectMany<InquiryServiceTypes>("dbo.proc_PayersInquiryServiceTypesSel", payerIdParam);
}

Conclusion

There are definitely things that are coded incorrectly in each of the sections. I'm pretty sure that there is a middle ground to be reached that is the most efficient code.

The code above has two points of inefficiency. The first is how it connects to the database in the first place. The second is once the data is returned, how do you deal with it. I'd love to discuss both, but feel that the first is more important for this point.

Thanks, C


Solution

  • I would say simply: there are abstractions that do this already (and do it very well). If you can handle creating the connection then, for example, with dapper-dot-net:

    return connection.Query<InquiryServiceTypes>(
            "dbo.proc_PayersInquiryServiceTypesSel",
            new { payerId }, commandType: CommandType.StoredProcedure);
    

    which will write all the parameterisation and materialization for you in heavily cached IL. No need to write a complex Fill method or populate method, and very very fast (identical performance to writing all the ADO.NET reader code manually, but without the boring code and chance of typos).

    Writing all those Fill methods manually is not efficient (for developer time).

    Note in the above; the anonymous type is defining the parameters, i.e. it is saying "there is an int parameter called payerId, with the same value as was passed in". You could also have:

    new { id = payerId, name = "abc", allData = true }
    

    which would add @id (int) with the value from payerId, @name (nvarchar) with value 'abc', and @allData (bit) with value 1.


    Edit re the points in the comments:

    • connection pooling is orthogonal since that is done automatically by default (with SQL server) as long as you release your connections promptly
    • entlib adds, IMO, overhead without any good reason. The code talking to entlib is virtually identical to talking to raw ado.net, except with more bloat and indirection. I would avoid entlib unless you actually use something it does to make your life easier
    • loading a dataset is always overhead; DataTable etc is complex - much more complex than loading a POCO model. Loading a dataset just so you can then use the dataset to load an object model is inefficient, and creates unnecessary garbage on the heap that needs to be collected, and has lots of steps anyway (adapters, etc), all of which take time
    • the DataTable approach also demands fully reading a table, rather than non-buffered spooling (possible with a raw reader and iterator block); this may be significant if you have very large results
    • of the two approaches presented, the second is IMO far preferable, but I do not like the interface; that is poor "separation of concerns" - the POCO's job is to represent a domain object, not to know about the database
    • as noted above, there are options similar to the second option, that are much less work to implement / maintain, and don't introduce SoC issues; I would look at this with great interest
    • such options also address more complex scenarios for you; multiple grids; horizontal joins (into sub-objects); etc