0

I am thinking about making a custom attribute so that when we are using multiple data readers [SqldataReader] on different objects/tables, we could use the attribute to get the type of the property, and the "columnName" of the property. This way, we could then have a method that takes the data reader as a param, and from there could reflect the attributes to read in the columns. An example of what is currently being done is below, and then an example of what I am trying to accomplish. The problem I am having, is how to manage how to tell it what the (Type) is.

 private static App GetAppInfo(SqlDataReader dr)
    {
        App app = new App();

        app.ID = MCCDBUtility.GetDBValueInt(dr, "APPLICATION_ID");
        app.Name = MCCDBUtility.GetDBValueString(dr, "APPNAME");
        app.Desc = MCCDBUtility.GetDBValueString(dr, "APPDESCRIPTION");
        app.Version = MCCDBUtility.GetDBValueString(dr, "APP_VERSION");
        app.Type = MCCDBUtility.GetDBValueString(dr, "APPLICATIONTYPEID");
        app.AreaName = MCCDBUtility.GetDBValueString(dr, "AREANAME");

        return app;
    }

What I am thinking though, so if I had a class for example like so:

[DataReaderHelper("MethodNameToGetType", "ColumnName")]
public string APPNAME {get;set;}

How could I go about this?

4
  • 1
    Are you asking how to get APPNAME as a string? Normally you would simply use an ORM like Entity Framework or Dapper to do this sort of mapping. Commented May 5, 2015 at 14:10
  • Perhaps I'm not quite understanding the full breadth of your objective, but it seems to me some (or at least a good part of this) is captured in Entity Framework. Have you taken a look at that possibility? It might prevent you from reinventing wheels :) Just a thought. Commented May 5, 2015 at 14:11
  • This is a solved problem and I'd really recommend Dapper in this case. Commented May 5, 2015 at 14:14
  • You're looking for an ORM. As @DavidW said, check out Entity Framework or NHibernate. Commented May 5, 2015 at 14:15

1 Answer 1

2

Fist of all, this is possible and if you like I could add a code sample. But: This is not a good idea.

Why, you ask?

First - DataReader provides you with a method GetSchemaTable() which contains a property DataType which is a System.Type object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName") that does the logic for your.

Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)

If you combine that you can achive what you want with

instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")

public T GetValue<T>(IDataReader reader, string columnName)
{
     object value GetValue(reader, columnName);
     return Convert.ChangeType(value, typeof(T));
}

private object GetValue(IDataReader reader, string columnName)
{
    var schmema = reader.GetSchemaTable();
    var dbType = typeof(object);
    foreach(DataRowView row in schema.DefaultView)
        if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
            return row["ColumnType"];

    if (dbType.Equals(typeof(int))
        return GetInt(reader, columnName)
    ... // you get the point
    else
        return GetObject(reader, columnName);
}

And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET, give it a try. https://github.com/StackExchange/dapper-dot-net

In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos

Update

As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.

Run the test and enjoy.

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var values = new Dictionary<string, object>();
        values.Add("ProductId", 17);
        values.Add("ProductName", "Something");
        values.Add("Price", 29.99M);

        var reader = new FakeDataReader(values);

        var product1 = new Product();
        reader.SetValue(product1, p => p.Id);
        reader.SetValue(product1, p => p.Name);
        reader.SetValue(product1, p => p.Price);

        Assert.AreEqual(17, product1.Id);
        Assert.AreEqual("Something", product1.Name);
        Assert.AreEqual(29.99M, product1.Price);

        var product2 = new Product();
        reader.SetAllValues(product2);

        Assert.AreEqual(17, product2.Id);
        Assert.AreEqual("Something", product2.Name);
        Assert.AreEqual(29.99M, product2.Price);
    }

}

public class Product
{
    [Mapping("ProductId")]
    public int Id { get; set; }

    [Mapping("ProductName")]
    public string Name { get; set; }

    public decimal Price { get; set; }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
    public MappingAttribute(string columnName)
    {
        this.ColumnName = columnName;
    }

    public string ColumnName { get; private set; }
}

public static class IDataReaderExtensions
{
    public static void SetAllValues(this IDataReader reader, object source)
    {
        foreach (var prop in source.GetType().GetProperties())
        {
            SetValue(reader, source, prop);
        }
    }
    public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
    {
        var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
        SetValue(reader, source, property);
    }

    private static void SetValue(IDataReader reader, object source, PropertyInfo property)
    {
        string propertyName = property.Name;
        var columnName = propertyName;
        var mapping = property.GetAttribute<MappingAttribute>();
        if (mapping != null) columnName = mapping.ColumnName;

        var value = reader.GetValue(reader.GetOrdinal(columnName));
        var value2 = Convert.ChangeType(value, property.PropertyType);
        property.SetValue(source, value2, null);
    }
}

public static class ICustomFormatProviderExtensions
{
    public static T GetAttribute<T>(this ICustomAttributeProvider provider)
    {
        return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
    }
}

public class FakeDataReader : IDataReader
{
    private Dictionary<string, object> values;

    public FakeDataReader(Dictionary<string, object> values)
    {
        this.values = values;
    }

    public int GetOrdinal(string name)
    {
        int i = 0;
        foreach (var key in values.Keys)
        {
            if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
            i++;
        }
        return -1;
    }

    public object GetValue(int i)
    {
        return values.Values.ToArray()[i];
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, and sounds good. The one thing I was wanting out of this, is to not have to enter the column name either. So the attribute wold be the column name, or even reflecting the property name, and having it match the column name. I see what you are saying though, and appreciate the feed back.
@Casey As promised I added a fully functional unit test which demonstrates how to do this but it uses reflection so I won't be very fast compared to Dapper or Entity Framework.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.