Search code examples
linq-to-sqllambdaprojection

LINQ: Expression.Call(typeof(Queryable), "Select"


I'm attempting to create a small "automapper-esq" utility that will take a LinqToSql entity and map it to a "projection class".

So far I have something like this:

class Entity
{
    public int ID { get; set; }
    public string WantedProperty { get; set; }
    public string UnWantedPropertyData { get; set; }
    ...More Unwanted Properties...
    public IEnumerable<ChildEntity> ChildEntities { get; set; }
}

class EntityProjection
{
    public int ID { get; set; }
    public string WantedProperty { get; set; }
    public IEnumerable<ChildEntityProjection> ChildEntities { get; set; }
}

class ChildEntityProjection
{
    public int ID { get; set; }
    public string WantedProperty { get; set; }
    public string UnWantedPropertyData { get; set; }
    ...More Unwanted Properties...
}


var results = context.Table.Select(ProjectionHelper.BuildProjection<Entity,EntityProjection>());

where BuildProjection returns:

Expression<Func<TSource, TResult>>

which essentially creates a lambda like this:

A => new EntityProjection() { ID = A.ID, WantedProperty = A.WantedProperty }

Now the tricky part...I'd like to be able to project association properties of the "parent" entity as well. Essentially what I need is to get something like this:

A => new EntityProjection() {
  ID = A.ID,
  WantedProperty = A.WantedProperty,
  ChildEntities = A.ChildEntities.Select(B => new ChildEntityProjection {
    ID = B.ID,
    WantedProperty = B.WantedProperty
  }
}

I have gotten as far as getting this part:

A => new EntityProjection() {
  ID = A.ID,
  WantedProperty = A.WantedProperty,
  ChildEntities = System.Collections.Generic.List1[ChildEntity].Select(B => new ChildEntityProjection {
    ID = B.ID,
    WantedProperty = B.WantedProperty
  }
}

By doing this:

IQueryable<ChildEntity> list = new List<ChildEtity>().AsQueryable();
Expression _selectExpression = Expression.Call(
  typeof(Queryable),
  "Select",
  new Type[] { typeof(ChildEntity), typeof(ChildEntityProjection) },
  Expression.Constant(list),
  _nestedLambda);

Here is where I am stuck at the moment...I am getting a little confused when attempting to replace Expression.Constant(list) with some other expression that represents the actual datatype for the property so that "System.Collections.Generic.List1[ChildEntity].Select(B=>..." will be replaced with "A.ChildEntities.Select(B=>..."

Any ideas?


Solution

  • I was looking more for how to do this using Expressions (correct terminology?) and I did eventually figure it out.

    I had to change this:

    IQueryable<ChildEntity> list = new List<ChildEtity>().AsQueryable(); 
    Expression _selectExpression = Expression.Call( 
      typeof(Queryable), 
      "Select", 
      new Type[] { typeof(ChildEntity), typeof(ChildEntityProjection) }, 
      Expression.Constant(list), 
      _nestedLambda); 
    

    to this:

    MethodInfo selectMethod = null;
    foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
      foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
        if (p.ParameterType.GetGenericArguments().Count() == 2)
          selectMethod = (MethodInfo)p.Member;
    
    var _selectExpression = Expression.Call(
      null,
      selectMethod.MakeGenericMethod(new Type[] { typeof(ChildEntity), typeof(ChildEntityProjection) }),
      new Expression[] { _myPropertyExpression, _myFuncExpression });
    

    Hope this helps someone else out...