Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Switching from Reflection to Expression trees

Since reflection on a row by row basis is rather expensive, I've been looking for a faster alternative to build and insert entities. I did some research research on the subject and also found some performance comparisons which seem to indicate that the Expression trees are the way to go. How would I rework the following function to take advantage of this?

    public static void InsertTable(IEnumerable<DataTable> chunkedTable)
    {

        Parallel.ForEach(
            chunkedTable,
            new ParallelOptions
            {
                MaxDegreeOfParallelism = Convert.ToInt32(ConfigurationManager.AppSettings["MaxThreads"])
            },
            chunk =>
            {
                Realty_Records_ProdEntities entities = null;
                try
                {
                    entities = new Realty_Records_ProdEntities();
                    entities.Configuration.AutoDetectChangesEnabled = false;

                    foreach (DataRow dr in chunk.Rows)
                    {
                        var parcelToInsert = new Parcel();

                        foreach (DataColumn c in dr.Table.Columns)
                        {
                            var propertyInfo = parcelToInsert.GetType()
                                .GetProperty(
                                    c.ColumnName,
                                    BindingFlags.SetProperty | BindingFlags.IgnoreCase
                                    | BindingFlags.Public | BindingFlags.Instance);

                            propertyInfo?.SetValue(
                                parcelToInsert,
                                TaxDataFunction.ChangeType(
                                    dr[c.ColumnName],
                                    propertyInfo.PropertyType),
                                null);
                        }
                        entities.Parcels.Add(parcelToInsert);
                    }
                    entities.SaveChanges();
                }
                catch (Exception ex)
                {
                    TaxDataError.AddTaxApplicationLog(
                        TaxDataConstant.CategoryError,
                        ex.Source,
                        ex.Message,
                        ex.StackTrace);
                    throw;
                }
                finally
                {
                    entities?.Dispose();
                }
            });
    }

EDIT:

Here is the solution I ended up implementing:

    private static readonly ConcurrentDictionary<SetterInfo, Action<object,object>> CachedSetters =
        new ConcurrentDictionary<SetterInfo, Action<object, object>>();

    private static readonly MethodInfo ChangeTypeMethod =
        ((Func<object, Type, object>) TaxDataFunction.ChangeType).Method;

    private static void SetProperty(object obj, string name, object value)
    {
        if (obj == null)
            return;

        var key = new SetterInfo(obj.GetType(), name);

        var setter = CachedSetters.GetOrAdd(key, CreateSetter);

        setter(obj, value);
    }

    private static Action<object, object> CreateSetter(SetterInfo info)
    {
        var propertyInfo = info.Type.GetProperty(info.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

        if (propertyInfo == null)
            return (s, v) => { };

        var objParameter = Expression.Parameter(typeof(object));
        var valueParameter = Expression.Parameter(typeof(object));

        var changeTypeCall = Expression.Call(ChangeTypeMethod, valueParameter, Expression.Constant(propertyInfo.PropertyType));

        var objCast = Expression.Convert(objParameter, info.Type);
        var valueCast = Expression.Convert(changeTypeCall, propertyInfo.PropertyType);

        var property = Expression.Property(objCast, propertyInfo);

        var assignment = Expression.Assign(property, valueCast);

        var lambda = Expression.Lambda<Action<object, object>>(assignment, objParameter, valueParameter);

        return lambda.Compile();
    }

    private struct SetterInfo
    {
        public Type Type { get; }
        public string Name { get; }

        public SetterInfo(Type type, string name)
        {
            Type = type;
            Name = name;
        }
    }
like image 455
jdm5310 Avatar asked Sep 11 '15 16:09

jdm5310


People also ask

What are the advantages of expression tree?

Expression trees allow you to build code dynamically at runtime instead of statically typing it in the IDE and using a compiler. They are well explained in the documentation.

Where are expression trees used?

Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y . You can compile and run code represented by expression trees.


1 Answers

static ConcurrentDictionary<string, Lazy<Action<object, object>>> CachedProperties = 
    new ConcurrentDictionary<string, Lazy<Action<object, object>>>();

static void SetProperty(object obj, string name, object value)
{
    if(obj==null)
        throw new ArgumentNullException("obj");
    Type objType = obj.GetType();
    string key =  objType.FullName + ":" + name;
    Action<object, object> f = 
    CachedProperties.GetOrAdd(key, k => 
        new Lazy<Action<object,object>>(() => {
            PropertyInfo prop = objType.GetProperty(name);
            if(prop==null){
                return (s,v) => {};
            }
            ParameterExpression pobj = 
                Expression.Parameter(typeof(object));
            ParameterExpression pval = 
                Expression.Parameter(typeof(object));
            Expression left = Expression.Property(
                Expression.TypeAs( pobj, objType), prop);
            Expression right = Expression.Convert(pval, prop.PropertyType);

            return Expression
            .Lambda<Action<object, object>>(
                Expression.Assign(left,right), pobj, pval).Compile();
        })).Value;

    f(obj,value);
}

Usage....

SetProperty(parcelToInsert, c.ColumnName, dr[c.ColumnName])
like image 195
Akash Kava Avatar answered Sep 25 '22 18:09

Akash Kava