5
\$\begingroup\$

I have have attempted to use generics and constraints to create functions that retrieve and save data from a number of similar objects. Each of these objects describe their own properties. When I use these methods for a particular Type I don't understand why I have to specify new() in the constraint and also create a new version of the type in order to access its underlying properties to assist me. I'm sure there is a better way, but my knowledge in this area is a little lacking.

There are a number of objects that inherit from RawDataAggregatedItem.

public class Project : RawDataAggregatedItem
{
    public override string IdColumnName
    {
        get { return "project_id"; }
    }

    public override string DescriptionColumnName
    {
        get { return "project_description"; }
    }

    public override string GetStoredProcedureName
    {
        get { return "R_projects_sp"; }
    }
}

...

public class Category : RawDataAggregatedItem
{
    public override string IdColumnName
    {
        get { return "asset_category"; }
    }

    public override string GetStoredProcedureName
    {
        get { return "R_asset_category_sp"; }
    }
}

RawDataAggregatedItem has it's own interface:

public interface IRawDataAggregatedItem
{
    string IdColumnName { get; }
    string DescriptionColumnName { get; }
}

public abstract class RawDataAggregatedItem : IRawDataAggregatedItem
{

    public abstract string IdColumnName { get; }
    public virtual string DescriptionColumnName
    {
        get { return string.Empty; }
    }
    public abstract string GetStoredProcedureName { get; }
}

I've created functions that get and save data to the database using generics expecting these different object types:


public RawDataAggregatedItems<T> GetRawDataAggregated<T>(int? id) where T : IRawDataAggregatedItem, new()
    {
        try
        {
            var itemType = new T();
            using (var cn = Connection())
            {
                var cmd = new SqlCommand(itemType.GetStoredProcedureName, cn) { CommandTimeout = _commandTimeout, CommandType = CommandType.StoredProcedure };
                cmd.Parameters.Add(new SqlParameter("@periodId", SqlDbType.Int)).Value = id;
                return new RawDataAggregatedItems<T>(cmd.ExecuteReader());
            }
        }
        catch (Exception ex)
        {
            throw new Exception("Error fetching aggregated rawdata list.", ex);
        }
    }

public void UpdateRawDataAggregated(RawDataAggregatedItem itemToUpdate)
    {
        try
        {
            using (var cn = Connection())
            {
                var cmd = new SqlCommand(itemToUpdate.UpdateStoredProcedureName, cn) { CommandTimeout = _commandTimeout, CommandType = CommandType.StoredProcedure };
                cmd.Parameters.Add(new SqlParameter("@projectid", SqlDbType.VarChar)).Value = ((IRawDataAggregatedItem)itemToUpdate).ID;
                cmd.Parameters.Add(new SqlParameter("@is_flagged", SqlDbType.Bit)).Value = ((IRawDataAggregatedItem)itemToUpdate).isFlaggedForReview;
                cmd.Parameters.Add(new SqlParameter("@is_reviewed", SqlDbType.Bit)).Value = ((IRawDataAggregatedItem)itemToUpdate).isReviewed;
                cmd.ExecuteReader();
            }
        }
        catch (Exception ex)
        {
            throw new Exception("Error updating aggregated rawdata item.", ex);
        }
    }

If feels a bit wrong to

  1. supply new() in the GetRawDataAggregated constraint but the compiler is forcing me to.
  2. New-ing up a Type T (var itemType = new T()) in order to get to T.Property.

Please could you suggest a better way of doing this?

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Well GetStoredProcedureName is an instance property - you need an instance of the object to get the value of the property. The value belongs to an instance, not to the type. You either need to use static properties and lose the generics or introduce a configuration source that maps type to a configuration object.

I'd sugges that the object has no need to know what its stored procedures are called so the latter suggestion seems to be the way to go.

Create a configuration class:

public class DataConfiguration<T>
{
    public string GetStoredProcedureName { get; set; }
} 

public class DataConfigurationProvider
{
    public DataConfiguration<T> GetConfiguration<T>()
    {
        // some stuff.
    }
}

Then you can update your functions:

public RawDataAggregatedItems<T> GetRawDataAggregated<T>(int? id) where T : IRawDataAggregatedItem
{
    // _dataConfigurationProvider injected by constructor injection.
    var sprocName = _dataConfigurationProvider.GetConfiguration<T>().GetStoredProcedureName;
    try
    {
        using (var cn = Connection())
        {
            var cmd = new SqlCommand(sprocName, cn) { CommandTimeout = _commandTimeout, CommandType = CommandType.StoredProcedure };
            cmd.Parameters.Add(new SqlParameter("@periodId", SqlDbType.Int)).Value = id;
            return new RawDataAggregatedItems<T>(cmd.ExecuteReader());
        }
    }
    catch (Exception ex)
    {
        throw new Exception("Error fetching aggregated rawdata list.", ex);
    }
}

I've been a bit wooly on the specifics here but hopefully you can see yourself to a working solution!

\$\endgroup\$
1
  • \$\begingroup\$ Sounds sensible and clean. will give it a go. cheers \$\endgroup\$
    – sarin
    Commented Jun 26, 2015 at 8:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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