views:

136

answers:

1

I am working a similar problem as Question 222511 I do need to use the MemberInit Expression so I can just add them to the constructor... I am trying to implement John Skeet's answer but I am running into a big performance difference. Here is some of the code:

// Method A:
// This work good, is fast and returns an un-executed query...
DataContext.LoanNote.Join<LoanNote, Customer, int, LoanNote>(
    DataContext.Customers, loanNote => loanNote.PrimaryCustomerNumber, customer => customer.CustomerNumber,
(LoanNote loanNote, Customer customer) => new LoanNote()
{
    AccountFeeBillAmount = loanNote.AccountFeeBillAmount,
    AccountOpenDate = loanNote.AccountOpenDate,
    // This goes on and on...
    PrimaryCustomer = customer
});

// Method B:
// This on the other hand is a lot slower and I am not sure why...
var resultSelector = BuildJoinResultSelector<LoanNote, Customer, LoanNote("PrimaryCustomer").Compile();

DataContext.LoanNote.Join<LoanNote, Customer, int, LoanNote>(
        DataContext.Customers, loanNote => loanNote.PrimaryCustomerNumber, customer => customer.CustomerNumber, resultSelector);


// The build MemberInitExpression method...
private static Expression<Func<TOuter, TInner, TResult>> BuildJoinResultSelector<TOuter, TInner, TResult>(string propertyName) where TResult : class
     {
      var result = default(Expression<Func<TOuter, TInner, TResult>>);
      var resultType = typeof(TResult);
      var outerType = typeof(TOuter);
      var innerType = typeof(TInner);
      var outer = Expression.Parameter(outerType, "outer");
      var inner = Expression.Parameter(innerType, "inner");
      var bindings = new List<MemberBinding>();

      foreach (var property in resultType.GetProperties())
      {
       if (property.CanRead == false)
       {
        continue;
       }
       else if (property.CanWrite == false)
       {
        continue;
       }
       else if (property.Name == propertyName)
       {
        var condition = Expression.Condition(Expression.Equal(inner, Expression.Constant(null)), Expression.New(innerType), inner);

        bindings.Add(Expression.Bind(property, condition));
       }
       else
       {
        bindings.Add(Expression.Bind(property, Expression.Property(outer, property)));
       }
      }

      var memberInit = Expression.MemberInit(Expression.New(resultType), bindings);

      result = Expression.Lambda<Func<TOuter, TInner, TResult>>(memberInit, outer, inner);

      return result;
     }
+1  A: 

The second method will be slower to execute because it uses reflection (the GetProperties call).

If you are calling it many times, you can cache the result of GetProperties like this:

static class PropertiesCache<T> { 
    public static readonly PropertyInfo[] Properties = typeof(T).GetProperties();
}

This will call GetProperties just once per type; use like this:

foreach (var property in PropertiesCache<TResult>.Properties) {
    if(!property.CanRead || !property.CanWrite) continue;

    //...
}


EDIT:

You can also replace your entire loop with a LINQ query, like this:

var memberInit = Expression.MemberInit(Expression.New(typeof(TResult)),
    from property in PropertiesCache<TResult>.Properties
    where property.CanRead && property.CanWrite
    select Expression.Bind(property, property.Name == propertyName ?
        Expression.Coalesce(inner, Expression.New(innerType))
        : Expression.Property(outer, property)
        )
    );
SLaks
I tried your suggestion and it didn't make any difference... I have stepped though the code and the BuildJoinResultSelector is only being called once and therefore the GetProperties is only being called once. So it should have that much of an effect on it. Thanks though...
J.13.L
Is the slowness occurring when building the expression or executing it?
SLaks
It is durring execution.
J.13.L
And I just checked the SQL that is generated and it is exactly the same. So it not the SQL query that is taking longer.
J.13.L