Может ли Reflection в .Net помочь построить объект с табличным параметром / SqlMetaData [] динамически, посмотрев на определенное пользователем имя типа? - PullRequest
5 голосов
/ 19 августа 2011

Я начал использовать табличные параметры в Sql Server 2k8 для пакетных операций.Мне очень понравилась эта функция, и я чувствую, что она пришла после долгого ожидания.

Однако, чтобы передать TVP из кода .Net, требуется слишком много тяжелой работы для создания SQLMetaData [] и последующего заполнения значений.в петле.

Как избежать сохранения сохраненных пользователем типов в объектах Sql Server и SQLMetaData [] в вашем .Net-коде при синхронизации?Когда я изменяю определение типа в SQL, нет простого способа узнать, где все я использовал этот тип в огромном коде .Net.

Может ли .Net Reflection спасти программиста для создания SQLMetadata, дав имя определяемого пользователем типа, и поможет заполнить данные, предоставив объектные массивы.

Рассмотрим этот пример:

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_TradingAllocationRule[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)
    {

    }
}

Теперь, если в вашей таблице 100 столбцов, представьте свой код.

Ответы [ 2 ]

3 голосов
/ 20 августа 2011

Вы можете сделать это, используя отражение. Во-первых, должен быть способ переопределить значения по умолчанию для имен и длин. Для этого определите Attribute s:

[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;
    }
}

И используйте их по своему типу:

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; }
}

Затем вы можете создать метод, который сопоставляет коллекцию этого типа с коллекцией 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();
}

В этом коде много размышлений, поэтому он может быть слишком медленным для вас. Если это так, вам нужно реализовать какое-то кэширование. Один из способов сделать это - создать Expression, который выполняет всю эту работу, и затем скомпилировать его в делегат (только .Net 4, потому что вам потребуется BlockExpression).

Кроме того, ваши фактические требования могут быть более сложными, потому что вам может потребоваться игнорировать некоторые свойства или что-то подобное. Но это должно быть легко добавить.

0 голосов
/ 20 августа 2011

Недостаточно в вопросе дать пример кода, но для чего-то подобного я бы сделал что-то вроде написания отдельного исполняемого файла .NET для чтения метаданных SQL и создания вспомогательных классов (очень похожих на ваш пример) каждый UDT. Преимущество генерации кода в том, что он немного быстрее во время выполнения и, что более важно, в том, что вы можете читать и просматривать исходный код, как если бы он был написан от руки. Это также не особенно сложно сделать - особенно сейчас, когда существует частичное ключевое слово.

...