views:

51

answers:

2

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?

A: 

Here's a suggested query. And you might want to change your IEnumerable<> in EntityProjection to a List<> to avoid a lazy load (if it matters...). Anyway, I hope this is helpful.

EntityProjection projection = context.Table
    .Where(entity => entity.ID == 123)
    .Select(entity => new EntityProjection()
    {
        ID = entity.ID,
        WantedProperty = entity.WantedProperty,
        ChildEntities = entity.ChildEntities
            .Select(child => new ChildEntityProjection()
            {
                ID = child.EmployeeID,
                WantedProperty = child.WantedProperty
            })
            .ToList()
    })
    .SingleOrDefault();
shaunmartin
A: 

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...

Mike