Problema de rendimiento de GetCustomAttributes (¿los árboles de expresión son la solución?)

c# expression-trees performance reflection

Pregunta

Tengo un problema de rendimiento porque uso la reflexión y GetCustomAttributes para el acceso a mis datos. El perfilador de rendimiento lo detectó. Tengo un método de extensión como este:

public static class DataRowExtensions
{
    /// <summary>
    /// Maps DataRow objecto to entity T depending on the defined attributes. 
    /// </summary>
    /// <typeparam name="T">Entity to map.</typeparam>
    /// <param name="rowInstance">DataRow instance.</param>
    /// <returns>Instance to created entity.</returns>
    public static T MapRow<T>(this DataRow rowInstance) where T : class, new()
    {
        //Create T item
        T instance = new T();

        IEnumerable<PropertyInfo> properties = typeof(T).GetProperties();
        MappingAttribute map;
        DataColumn column;

        foreach (PropertyInfo item in properties)
        {
            //check if custom attribute exist in this property
            object[] definedAttributes = item.GetCustomAttributes(typeof(MappingAttribute), false);

            // Tiene atributos
            if (definedAttributes != null && definedAttributes.Length == 1)
            {
                //recover first attribute
                map = definedAttributes.First() as MappingAttribute;

                column = rowInstance.Table.Columns.OfType<DataColumn>()
                                          .Where(c => c.ColumnName == map.ColumnName)
                                          .SingleOrDefault();

                if (column != null)
                {
                    object dbValue = rowInstance[column.ColumnName];
                    object valueToSet = null;

                    if (dbValue == DBNull.Value)//if value is null
                        valueToSet = map.DefaultValue;
                    else
                        valueToSet = dbValue;

                    //Set value in property 
                    setValue<T>(instance, item, valueToSet);
                }
            }
        }

        return instance;
    }

    /// <summary>
    /// Set "item" property.
    /// </summary>
    /// <typeparam name="T">Return entity type</typeparam>
    /// <param name="instance">T type instance</param>
    /// <param name="item">Property name to return value</param>
    /// <param name="valueToSet">Value to set to the property</param>
    private static void setValue<T>(T instance, PropertyInfo item, object valueToSet) where T : class, new()
    {
        if (valueToSet == null)
        {
            CultureInfo ci = CultureInfo.InvariantCulture;

            if (item.PropertyType.IsSubclassOf(typeof(System.ValueType)))
            {
                //if is a value type and is nullable
                if (item.PropertyType.FullName.Contains("System.Nullable"))
                {
                    item.SetValue(instance, null, BindingFlags.Public, null, null, ci);
                }
                else
                {
                    item.SetValue(instance, Activator.CreateInstance(item.PropertyType, null), BindingFlags.Public, null, null, ci);
                }
            }
            else //property type is reference type
            {
                item.SetValue(instance, null, BindingFlags.Public, null, null, ci);
            }
        }
        else // set not null value
        {
            //if is a value type and is nullable
            if (item.PropertyType.FullName.Contains("System.Nullable"))
            {
                item.SetValue(instance, Convert.ChangeType(valueToSet, Nullable.GetUnderlyingType(item.PropertyType)), null);
            }
            else
            {
                item.SetValue(instance, Convert.ChangeType(valueToSet, item.PropertyType), null);
            }
        }
    }
}

Lo que hago aquí, en esencia, es mapear las entidades de dominio con los campos de la base de datos, y un ayudante de datos ataca las tablas automáticamente. Un ejemplo de una de estas entidades es:

public class ComboBox
    {
    /// <summary>
    /// Represents a ComboBox item.
    /// </summary>
    [Mapping("CODE", DefaultValue = 0, DBType = DbParametersTypes.Varchar2, IsKey = true, IdentifierFK = "")]
    public string Code { get; set; }

    /// <summary>
    /// Represents Text.
    /// </summary>
    [Mapping("DESCRIPTION", DefaultValue = "", DBType = DbParametersTypes.Varchar2, IsKey = false, IdentifierFK = "")]
    public string Description { get; set; }

    }

Y la clase de atributo que uso:

public sealed class MappingAttribute : Attribute
    {
        public string ColumnName { get; set; }

        public object DefaultValue { get; set; }

        public DbParametersTypes DBType { get; set; }

        public bool IsKey { get; set; }

        public string IdentifierFK { get; set; }

        public bool IsParameter { get; set; } 

        public MappingAttribute(string columnName)
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ArgumentNullException("columnName");

            ColumnName = columnName;
        }               
    }

Leí aquí que una posible mejora podría ser un árbol de expresiones, pero, primero, no soy un experto en expresiones, y segundo, tengo que resolver esto con .NET 3.5 ... (en la muestra .NET 4 o 4.5 se utiliza ...)

¿Sugerencias?

Gracias por adelantado.

Respuesta aceptada

public static class DataRowExtensions
{
    /// <summary>
    /// Maps DataRow objecto to entity T depending on the defined attributes. 
    /// </summary>
    /// <typeparam name="T">Entity to map.</typeparam>
    /// <param name="rowInstance">DataRow instance.</param>
    /// <returns>Instance to created entity.</returns>
    public static T MapRow<T>( this DataRow rowInstance ) where T : class, new()
    {
        //Create T item
        var instance = new T();
        Mapper<T>.MapRow( instance, rowInstance );
        return instance;
    }

    #region Nested type: Mapper

    private static class Mapper<T>
        where T : class
    {
        private static readonly ItemMapper[] __mappers;

        static Mapper()
        {
            __mappers = typeof (T)
                .GetProperties()
                .Where( p => p.IsDefined( typeof (MappingAttribute), false ) )
                .Select( p => new
                {
                    Property = p,
                    Attribute = p
                                  .GetCustomAttributes( typeof (MappingAttribute), false )
                                  .Cast<MappingAttribute>()
                                  .FirstOrDefault()
                } )
                .Select( m => new ItemMapper( m.Property, m.Attribute ) )
                .ToArray();
        }

        public static void MapRow( T instance, DataRow row )
        {
            foreach ( var mapper in __mappers )
            {
                mapper.MapRow( instance, row );
            }
        }

        #region Nested type: ItemMapper

        private sealed class ItemMapper
        {
            private readonly MappingAttribute _attribute;
            private readonly PropertyInfo _property;

            public ItemMapper( PropertyInfo property, MappingAttribute attribute )
            {
                _property = property;
                _attribute = attribute;
            }

            public void MapRow( T instance, DataRow rowInstance )
            {
                //TODO: Implement this with the code already provided
            }
        }

        #endregion
    }

    #endregion
}

La primera vez que se llama al método de extensión para un <T> dado, el constructor estático ejecutará y almacenará en caché un Mapper instancia para cada propiedad que tenga un MappingAttribute . Luego, para cada llamada después de eso, utilizará los mapeadores en caché para hacer la copia real.

También puede hacer que Mapper abstracto, y usar una subclase diferente para cada rama en su setValue<T>() . De esa manera, la mayor parte de tu reflexión solo ocurre una vez



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow