开发者

Can Reflection in .Net help construct a Table Valued Parameter/SqlMetaData[] Object Dynamically by looking at the User Defined TypeName?

I have started using Table Valued Parameters in Sql Server 2k8 for batch operations. I liked this feature a lot and feel it came after a long wait.

However, inorder to pass a TVP from .Net code there is too much of hardwork involved to construct the SQLMetaData[] and then filling up values in a loop.

How do you avoid the maintenance of keeping the User Defined Types in Sql Server and SQLMetaData[] objects in your .Net code in Synchronization? When i change a type definition in SQL, there is no easy way of knowing where all did I use that type in huge code of .Net.

Can .Net Reflection rescue a programmer to construct SQLMetadata by giving the name of User Defined Type and help in filling the data by providing object arrays.

Consider This Example:

SqlMetaData[] tvp_TradingAllocationRule = new SqlMetaData[13];
try
{
    tvp_TradingAllocationRule[0] = new SqlMetaData("ID", SqlDbType.UniqueIdentifier);
    tvp_TradingAllocationRule[1] = new SqlMetaData("Name", SqlDbType.VarChar, 255);
    tvp_TradingAllocationRule[2] = new SqlMetaData("Description", SqlDbType.VarChar, -1);
    tvp_TradingAllocationRule[3] = new SqlMetaData("Enabled", SqlDbType.Bit);
    tvp_TradingAllocationRule[4] = new SqlMetaData("Category", SqlDbType.VarChar, 255);
    tvp_TradingA开发者_开发知识库llocationRule[5] = new SqlMetaData("Custom1", SqlDbType.VarChar, 255);
    tvp_TradingAllocationRule[6] = new SqlMetaData("Custom2", SqlDbType.VarChar, 255);
    tvp_TradingAllocationRule[7] = new SqlMetaData("Custom3", SqlDbType.VarChar, 255);
    tvp_TradingAllocationRule[8] = new SqlMetaData("CreatedBy", SqlDbType.VarChar, 20);
    tvp_TradingAllocationRule[9] = new SqlMetaData("CreatedTS", SqlDbType.DateTime);
    tvp_TradingAllocationRule[10] = new SqlMetaData("ModifiedBy", SqlDbType.VarChar, 20);
    tvp_TradingAllocationRule[11] = new SqlMetaData("ModifiedTS", SqlDbType.DateTime);
    tvp_TradingAllocationRule[12] = new SqlMetaData("IsFactory", SqlDbType.Bit);
}
catch (Exception ex)
{
    throw new Exception("Error Defining the tvp_TradingActionCondition in .Net" + ex.Message);
}

foreach (TradingRuleMetadata ruleMetadata in updatedRules)
{
    SqlDataRecord tradingAllocationRule = new SqlDataRecord(tvp_TradingAllocationRule);
    try
    {
        tradingAllocationRule.SetGuid(0, ruleMetadata.ID);
        tradingAllocationRule.SetString(1, ruleMetadata.Name);
        tradingAllocationRule.SetString(2, ruleMetadata.Description);
        tradingAllocationRule.SetBoolean(3, ruleMetadata.Enabled);
        tradingAllocationRule.SetString(4, ruleMetadata.Category);
        tradingAllocationRule.SetString(5, ruleMetadata.Custom1);
        tradingAllocationRule.SetString(6, ruleMetadata.Custom2);
        tradingAllocationRule.SetString(7, ruleMetadata.Custom3);
        tradingAllocationRule.SetString(8, ruleMetadata.CreatedBy);
        tradingAllocationRule.SetDateTime(9, ruleMetadata.CreatedDate);
        tradingAllocationRule.SetString(10, ruleMetadata.ModifiedBy);
        tradingAllocationRule.SetDateTime(11, ruleMetadata.ModifiedDate);
        tradingAllocationRule.SetBoolean(12, ruleMetadata.IsFactory);
        tvp_TradingAllocationRuleRecords.Add(tradingAllocationRule);
    }
    catch (Exception ex)
    {

    }
}

Now if your table has 100 columns, imagine your code.


You can do that using reflection. First, there has to be a way to override the default values for names and lengths. To do that, define Attributes:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class LengthAttribute : Attribute
{
    private readonly int m_length;
    public int Length
    {
        get { return m_length; }
    }

    public LengthAttribute(int length)
    {
        m_length = length;
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class ColumnNameAttribute : Attribute
{
    private readonly string m_name;
    public string Name
    {
        get { return m_name; }
    }

    public ColumnNameAttribute(string name)
    {
        m_name = name;
    }
}

And use them on your type:

class TradingRuleMetadata
{
    public Guid ID { get; set; }

    public string Name { get; set; }

    [Length(-1)]
    public string Description { get; set; }

    public bool Enabled { get; set; }

    public string Category { get; set; }

    public string Custom1 { get; set; }

    public string Custom2 { get; set; }

    public string Custom3 { get; set; }

    [Length(20)]
    public string CreatedBy { get; set; }

    [ColumnName("CreatedTS")]
    public DateTime CreatedDate { get; set; }

    [Length(20)]
    public string ModifiedBy { get; set; }

    [ColumnName("ModifiedTS")]
    public DateTime ModifiedDate { get; set; }

    public bool IsFactory { get; set; }
}

Then you can create a method that maps a collection of this type to collection of SqlDataRecord:

private static readonly Dictionary<Type, SqlDbType> SqlDbTypes =
    new Dictionary<Type, SqlDbType>
    {
        { typeof(Guid), SqlDbType.UniqueIdentifier },
        { typeof(string), SqlDbType.VarChar },
        { typeof(bool), SqlDbType.Bit },
        { typeof(DateTime), SqlDbType.DateTime }
    };

static IList<SqlDataRecord> GetDataRecords<T>(IEnumerable<T> data)
{
    Type type = typeof(T);

    var properties = type.GetProperties();

    SqlMetaData[] metaData = new SqlMetaData[properties.Length];
    try
    {
        for (int i = 0; i < properties.Length; i++)
        {
            var property = properties[i];

            string name = property.Name;
            var columnNameAttribute = GetAttribute<ColumnNameAttribute>(property);
            if (columnNameAttribute != null)
                name = columnNameAttribute.Name;

            var dbType = SqlDbTypes[property.PropertyType];

            if (dbType == SqlDbType.VarChar)
            {
                int length = 255;

                var lengthAttribute = GetAttribute<LengthAttribute>(property);
                if (lengthAttribute != null)
                    length = lengthAttribute.Length;

                metaData[i] = new SqlMetaData(name, dbType, length);
            }
            else
                metaData[i] = new SqlMetaData(name, dbType);
        }
    }
    catch (Exception ex)
    {
        throw new Exception();
    }

    var records = new List<SqlDataRecord>();
    foreach (T item in data)
    {
        SqlDataRecord record = new SqlDataRecord(metaData);
        try
        {
            var values = properties.Select(p => p.GetValue(item, null)).ToArray();
            record.SetValues(values);
            records.Add(record);
        }
        catch (Exception ex)
        {

        }
    }
    return records;
}

static T GetAttribute<T>(PropertyInfo property)
{
    return (T)property.GetCustomAttributes(typeof(T), true).SingleOrDefault();
}

This code is uses quite a lot of reflection, so it can be too slow for you. If that's the case, you would need to implement some kind of caching. One way of doing that would be creating an Expression that does all this work and then compiling it into a delegate (.Net 4 only, because you would need BlockExpression).

Also, your actual requirements may be more complicated, because you may need to ignore some properties, or something similar. But that should be easy to add.


There isn't enough in the question to give a code sample, but for something like this I would do something like write a separate .NET executable to read the SQL metadata and generate helper classes (looking much like your example) for each UDT. The advantage of code generation is that it's a little faster at run time, and, more importantly, that you can read and step through the source code just as if it were hand-written. It's also not especially hard to do - especially now that the partial keyword exists.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜