0

I'm trying to write a dynamic LINQ query using Expressions (for the first time) to achieve this:

public override IQueryable<SelectListDto> GetSelectList()
{
    var ageGroups = DbContext.Set<AgeGroup>()
        .Select(x => new SelectListDto() { ID = x.ID, Name = x.Description_Chinese });
    return ageGroups;
}

The SelectListDto class looks like this:

public class SelectListDto : IRequestDto
{
    public int ID { get; set; }
    public string Name { get; set; }
}

Here's the code for my Expression, which I tried to adapt from the code give in the answer to this question:

protected Func<TEntity, SelectListDto> GetSelectListStatement()
{
    var entityType = typeof(TEntity);
    var entityIDProperty = entityType.GetProperty("ID");
    var entityNameProperty = entityType.GetProperty(GetPropertyNameForSelectListDescription()); // Returns the name of the property on TEntity to map to SelectListDto.Name
    var parameterExpression = Expression.Parameter(typeof(TEntity), "x"); // "x" in x => ..., linked to the TEntity
    var newExpression = Expression.New(typeof(SelectListDto)); // "new SelectListDto()"
    var idMemberExpression = Expression.Property(parameterExpression, entityIDProperty); // "x.ID"
    var nameMemberExpression = Expression.Property(parameterExpression, entityNameProperty); // "x.Name"
    var idBinding = Expression.Bind(entityIDProperty, idMemberExpression); // "ID = x.ID"
    var nameBinding = Expression.Bind(entityNameProperty, nameMemberExpression); // "Name == x.Name"
    var bindings = new List<MemberAssignment>() { idBinding, nameBinding };
    var memberInitExpression = Expression.MemberInit(newExpression, bindings); // initialization "new SelectListDto { ID = x.ID, Name = x.Name }"
    var lambda = Expression.Lambda<Func<TEntity, SelectListDto>>(memberInitExpression, parameterExpression); // // expression "x => new SelectListDto { ID = x.ID, Name = x.Name }"
    return lambda.Compile();
}

But this code throws the Exception

"ID" is not a member of type "SelectListDto"

It seems pretty clear to me that ID is a member of SelectList. So I am stumped at this point.

EDIT:

Here's the code that worked after being guided to the solution by Asad:

protected Func<TEntity, SelectListDto> GetSelectListStatement()
{
    var entityType = typeof(TEntity);
    var selectListDtoType = typeof(SelectListDto);
    var entityIDProperty = entityType.GetProperty("ID");
    var entityNameProperty = entityType.GetProperty(GetPropertyNameForSelectListText());
    var selectListDtoIDProperty = selectListDtoType.GetProperty("ID");
    var selectListDtoNameProperty = selectListDtoType.GetProperty("Name");

    var parameterExpression = Expression.Parameter(entityType, "x");
    var newInstantiationExpression = Expression.New(selectListDtoType);

    var idMemberExpression = Expression.Property(parameterExpression, entityIDProperty);
    var nameMemberExpression = Expression.Property(parameterExpression, entityNameProperty);

    var idBinding = Expression.Bind(selectListDtoIDProperty, idMemberExpression);
    var nameBinding = Expression.Bind(selectListDtoNameProperty, nameMemberExpression);
    var bindings = new List<MemberAssignment>() { idBinding, nameBinding };

    var memberInitExpression = Expression.MemberInit(newInstantiationExpression, bindings);
    var lambda = Expression.Lambda<Func<TEntity, SelectListDto>>(memberInitExpression, parameterExpression);
    return lambda.Compile();
}
Community
  • 1
  • 1
Hanshan
  • 3,656
  • 5
  • 29
  • 36
  • It says `SelectList` not `SelectListDto`? – dmeglio May 02 '15 at 00:50
  • And why does your class have Name but you're referencing Description_Chinese. Is this the wrong code? And what line is throwing the exception? The more details the better. – dmeglio May 02 '15 at 00:51
  • Sorry, that "SelectList" was a typo, should always have been "SelectListDto" – Hanshan May 02 '15 at 00:52
  • As for Name vs Description_Chinese, the Name is supposed to be generic. I changed the code to move attention away from this when I posted. But I will put the original back because it makes more sense. SelectListDto.Name should map in this particular case to Description_Chinese. – Hanshan May 02 '15 at 00:55
  • While I appreciate you trying to make it generic, if the code isn't valid, it's hard to spot the "real" problem. – dmeglio May 02 '15 at 00:56
  • `var idBinding = Expression.Bind(entityIDProperty, idMemberExpression); // "ID = x.ID"` looks fishy, given that `entityIDProperty` is from `TEntity` and not `SelectListDto`, though I have a good chance of being completely wrong here. – adamdc78 May 02 '15 at 00:56

1 Answers1

0

I'll remove some of the extra stuff so the problem is a bit easier to spot:

// Get PropertyInfo from TEntity
var entityIDProperty = entityType.GetProperty("ID");

...

// Create an instantiation expression for SelectListDto
var newExpression = Expression.New(typeof(SelectListDto));

...

// Create a member initialization expression for TEntity's ID property
var idBinding = Expression.Bind(entityIDProperty, idMemberExpression);

...

// Try to instantiate an SelectDto with an initializer 
// that initializes TEntity's ID property. This makes no sense!
var memberInitExpression = Expression.MemberInit(newExpression, bindings);

As you can see above, there's a mismatch between the property for which you have a binding expression and the type you're instantiating.

The solution depends on what you're trying to do. The signature for the func you're returning is kind of screwy in that you're doing the exact opposite of what is required to create a func with that signature. You're creating a func that accepts a TEntity and returns a new SelectListDto (although you're not doing this correctly, as mentioned above). When compiled as a lambda, this would be Func<SelectListDto, TEntity>, not Func<TEntity, SelectListDto>.

If you really do want to return a Func<TEntity, SelectListDto>, you need to make the parameter expression of type SelectListDto and the instantiation expression of type TEntity, and then you should be good.

If on the other hand you want to return a Func<SelectListDto, TEntity>, you need to change entityIDProperty and entityNameProperty to be properties from the SelectListDto.

Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • Thank you very much, Asad. Your answer helped me to see what was wrong in my code. The member expressions were for the TEntity, the bindings needed to be for the SelectListDto properties. – Hanshan May 05 '15 at 03:06