6

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;
        }
    }
Community
  • 1
  • 1
jdm5310
  • 79
  • 5
  • It's worth noting that compiling an expression tree will have a significant performance hit up front, in exchange for better speeds afterward. – StriplingWarrior Sep 11 '15 at 17:28

1 Answers1

3
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])
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • I was unable to get this to compile when I dropped it into my solution. I tried removing all the internals to see if I could get the base underlying issue (this might be totally wrong) and I got the error : Cannot implicitly convert type 'System.Lazy>' to 'System.Func' – jdm5310 Sep 22 '15 at 20:27
  • 1
    @jdm5310: `new Lazy>{` should be `new Lazy>(() => {` – StriplingWarrior Oct 04 '15 at 20:48
  • Not sure if you saw my edit, but I ended up separating things out a little more and ditching the Lazy layer entirely (I wasn't sure what the point was supposed to be in combination with a ConcurrentDictionary). Is there a performance benefit I'm missing out on by removing it or in general between my solution and Akash Kava's? – jdm5310 Oct 06 '15 at 19:06
  • Creating expression and compiling it everything is very slow, this is the reason I use concurrent dictionary which reuses compiled code. – Akash Kava Oct 06 '15 at 19:08