Skip to main content
GameDev.net gamedev.net
🔒 Locked

[.net] data abstraction layer

Started by capn_midnight Mar 26, 2009 at 2:07 PM 1 replies 1.1k views
Original Post
capn_midnight
capn_midnight
in .NET, is there a way to reflect upon what method is calling another method? Say ClassA.MethodA() calls ClassB.MethodB(), is there any way for MethodB to know it was MethodA that called it? This question is motivated by the dubious sort of ORM system I've created:

// these classes are in a common utility library
public abstract class DataAdapter<T> where T : new()
{
    public virtual T Load(DataRow row)
    {
        T inst = default(T);
        if (row != null)
        {
            FieldInfo[] fields = typeof(T).GetFields();
            inst = new T();
            // values get mapped based on reflected field name
            foreach (FieldInfo field in fields)
                if (row.Table.Columns.Contains(field.Name))
                    field.SetValue(inst, row[field.Name]);  
        }
        return inst;
    }
}
public abstract class DataAccess
{
    // variable length argument list FTW
    protected List<T> GetList<T>(string procName, params SqlParameter[] parameters)  
        where T : DataAdapter<T>, new()
    {
        List<T> list = new List<T>();
        //this code is actually refactored differently from here, this is just a canonical example
        using (DataSet set = new DataSet())
        {
            using (SqlCommand command = ConstructCommand(procName, parameters))
            {
                SqlCommand command = new SqlCommand();
                command.Connection = sql;
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = procName;
                // feeding parameters into the stored procedure command
                if (parameters != null)
                    foreach (SqlParameter parameter in parameters)
                        if (parameter.Value != null)
                            command.Parameters.Add(parameter);
                using (SqlDataAdapter adapter = new SqlDataAdapter(command))
                    adapter.Fill(set);
            }
            if (set != null && set.Tables.Count > 0 && set.Tables[0].Rows.Count > 0;)
                foreach (DataRow row in set.Tables[0].Rows)
                    list.Add(new T().Load(row));
        }
        return list;
    }
}

This may or may not seem sort of crazy, but it's neat because it lets me do this in implementing classes:

//these classes are in a project that uses the utility library
public class SiteUsageBasic : DataAdapter<SiteUsageBasic>
{
    public int SiteUsageRecordID;  // concrete class is very simple
    public string Email, CraneModel;
    public DateTime SearchDate;
}
public class ConcreteDataAccess : DataAccess
{
    public List<SiteUsageBasic> SearchSiteUsage(string email, int modelID, DateTime start, DateTime end)
    {
        // how can we make it to where we don't have to explicitly instantiate the SqlParameter objects?
        return GetList<SiteUsageBasic>("SearchSiteUsage",
            new SqlParameter("@Email", email), 
            new SqlParameter("@ModelID", modelID),
            new SqlParameter("@DateStart", start),
            new SqlParameter("@DateEnd", end));
    }
}

This works because we dictate at a project level that the fields of the "SiteUsageBasic" class have to match the columns of whatever the "SearchSiteUsage" stored procedure returns. It works fine, but I want it to be even easier to use (I have a jr. programmer who is a bit dodgy on his programming skills). How can I make it to where we don't have to explicitly instantiate the SqlParameter objects? If I can figure that out, then the data access stuff is a full abstraction away from the underlying database system. Some possible solutions: 1. Use the varargs list to alternate param name and param value, and iterate over it by twos. It would look like this:

GetList<SiteUsageBasic>("SearchSiteUsage",
    "@Email", email, 
    "@ModelID", modelID,
    "@DateStart", start,
    "@DateEnd", end);
this is kind of yick 2. query the DB for the stored procedures it has, with params, and procedure records in a Dictionary. Use this to construct the order of parameters. It would look like this:

GetList<SiteUsageBasic>("SearchSiteUsage",
    email, 
    modelID,
    start,
    end);
This is better, but not quite there. 3. somehow reflect over the ConcreteDataAccess class' method calls' names and parameters to automatically fill in SP name and parameter names. It would look like this:

GetList<SiteUsageBasic>(email, modelID, start, end);
If I had a system like this, I could mandate on the project level that the calling method name would have to match a name of a Stored Procedure, and its parameters would have to match the parameters for the stored procedure. However, I don't know if its possible for GetList to know what method is calling it.
capn_midnight
capn_midnight
gah, I completely brain farted on "Stack Trace". That was exactly what I needed. I knew it could work somehow.

Well, here is the edited code for the abstract DataAccess class, if anyone is interested. This ConstructCommand method is called by methods that execute stored procedures that do or do not return results:
protected SqlCommand ConstructCommand(params object[] parameters){    SqlCommand command = new SqlCommand();    command.Connection = sql;    command.CommandType = CommandType.StoredProcedure;    MethodBase method = this.FindCaller();    if (method != null)    {        command.CommandText = method.Name;        if (parameters != null)        {            ParameterInfo[] paramInfos = method.GetParameters();            for (int i = 0; i < paramInfos.Length && i < parameters.Length; ++i)                if (parameters != null)                    command.Parameters.Add(                        new SqlParameter("@" + paramInfos.Name,                        parameters));        }    }    else        throw new Exception("Could not find command");    return command;}private static Type DataAccessType = typeof(DataAccess);private MethodBase FindCaller(){    StackTrace trace = new StackTrace();    MethodBase method = null;    for (int i = 0; i < trace.FrameCount && method == null; ++i)    {        method = trace.GetFrame(i).GetMethod();        if(method.DeclaringType == DataAccessType || !method.DeclaringType.IsSubclassOf(DataAccessType))            method = null;    }    return method;}

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.