Create Generic Expression from string property name
Asked Answered
P

1

19

I have a variable called sortColumn, which contains the text of a column that I want to sort a query result by. I also have a generic repository which takes as a parameter an Expression that contains the field I want to sort by. I can't seem to get from the string property name to an Expression.

So the generic repository that I have contains the following method

public IEnumerable<TEntity> Get<TOrderBy>(Expression<Func<TEntity, bool>> criteria,
                                          Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex,
                                          int pageSize,
                                          bool isAssendingOrder = true,
                                          EnumDeletePolicy deletePolicy = EnumDeletePolicy.ExcludeDeleted)

Notice the second parameter to this Get is Expression-Func-TEntity, TOrderBy. As I mentioned I have a variable called sortColumn, which contains the string for a property on my TEntity object I need to convert this string into an Expression that I can pass to the Get method.

Here is what I have right now.

        var parameter = Expression.Parameter(typeof(IContract));
        var memberExpression = Expression.Property(parameter, data.SortColumn);
        var lambdaExpression = Expression.Lambda(memberExpression, parameter);

Which creates an object of type LambdaExpression. The actual type of this LambdaExpression is an Expression-Func-IContract, string (or whatever the type sortColumn of the property is). If I call the Get method and pass in this LambdaExpression and explicitly cast it to the Expression type then it will work fine. The problem is I don't know what the Expression type is, it could be a string, int, int?, etc. It all depends on the type of the property that is specific in the sortColumn property.

Can you help me make this last jump to the right Expression type?

Edit based on Marc's suggestions: I nearly have this working, actually based specifically on the question it is working, but I have 1 remaining problem.

The IContract which is the Entity Type that I'm querying against actually inherits from IRelationship. If I specify a field from the IContract interface then the code above works. If I specify a field from the IRelationship interface then the following line fails.

        var memberExpression = Expression.Property(parameter, data.SortColumn);

If I try something like below so that I'm grabbing the MemberExpression from the IRelationship, but building the Lambda based on IContract I get an error from the repository.

        var parameter = Expression.Parameter(typeof(IRelationship));
        var memberExpression = Expression.Property(parameter, data.SortColumn);
        var orderBy = Expression.Lambda(memberExpression, Expression.Parameter(typeof(IContract)));

The error that I get is "The parameter '' was not bound in the specified LINQ to Entities query expression."

The final expression to get it working was this

        var parameter = Expression.Parameter(typeof(IContract));
        var memberExpression = Expression.Property(parameter, typeof(IRelationship), data.SortColumn);
        var orderBy = Expression.Lambda(memberExpression, parameter);

So I needed to specify the middle parameter to the memberExpression line, to say look in the inherited Relationship interface for the property

Propaedeutic answered 19/8, 2013 at 18:54 Comment(1)
What is it you want to do with the expression? There are ways to use dynamic to get it to flip into the most appropriate generic overload, basically avoiding MakeGenericMethod. Any use? For example: IQueryable<SomeType> filtered = Queryable.Where(source, (dynamic)expression);Hostelry
H
23

You kinda need to use the correct generic overload - which used to mean you had to use MakeGenericMethod; however, you can also use dynamic to avoid the need to use MakeGenericMethod here, for example (in this case via Where, but the important point is how it works):

IQueryable<Foo> source = new[] { new Foo { Bar = 123 } }.AsQueryable();
Expression<Func<Foo,bool>> typed =  x=>x.Bar == 123;

LambdaExpression untyped = typed;
IQueryable<Foo> filtered = Queryable.Where(source, (dynamic)untyped);

Note: you can't use extension methods here - hence why you need to use Queryable.*.

For an OrderBy example using your code:

var parameter = Expression.Parameter(typeof(Foo));
var memberExpression = Expression.Property(parameter, "Bar");
var lambdaExpression = Expression.Lambda(memberExpression, parameter);
LambdaExpression untyped = lambdaExpression;

IQueryable<Foo> sorted = Queryable.OrderBy(source, (dynamic)untyped);

var all = sorted.ToArray();

Re the edit:

var parameter = Expression.Parameter(typeof(IRelationship));
var memberExpression = Expression.Property(
    Expression.Convert(parameter, typeof(IContract)), data.SortColumn);
var orderBy = Expression.Lambda(memberExpression, parameter);
Hostelry answered 19/8, 2013 at 19:9 Comment(12)
But the method that I need to call takes an Expression-Func not a dynamic, so I can't pass in the a dynamic.Propaedeutic
@PaulCavacas Queryable.Where and Queryable.OrderBy also take an Expression-Func, not a dynamic; that isn't a problem. The point is that the use of dynamic here makes it work. Magic.Hostelry
I nearly have it working. One remaining problem. Se question for further details.Propaedeutic
I get Instance property 'RelationshipNumber' is not defined for type IContract. RelationshipNumber is on the IRelationship interfacePropaedeutic
@Paul then... you don't need to mention IContract at all - just Expression.Property(parameter, data.SortColumn) - passing parameter on the last lineHostelry
That is what I originally tried, but the Generic Repository is specified to be of type IContract and if I pass in an Expression that is of type IRelationship then it fails as soon as it tries to call into the repositoryPropaedeutic
@Paul then you need to make the parameter of type IContract, and reverse the convert to get to IRelationshipHostelry
I get this error "Unable to cast the type 'IContract' to type 'IRelationship'. LINQ to Entities only supports casting EDM primitive or enumeration types." When I do this var parameter = Expression.Parameter(typeof(IContract)); var memberExpression = Expression.Property(Expression.Convert(parameter, typeof(IRelationship)), data.SortColumn); var orderBy = Expression.Lambda(memberExpression, parameter);Propaedeutic
I got it. See question for final answerPropaedeutic
@MarcGravell I have built an expression kind of like you describe. It works great for e.g LessThan or GreaterThan expressions. But if I add two expressions together, it uses the '&' sign, I require it to be the string 'AND'. Can you tell me how to fix this, please?Trilly
@Trilly what do require to be the string AND? the ToString() representation? if that doesn't meet your needs, you'll have to implement it manuallyHostelry
@MarcGravell Hi, I have discovered that I must use .AndAlso, then I get the correct behavior: // Combine expressions using AndAlso => 'AND' operand. Do not use And => '&' queryExpression = Expression.AndAlso(smallerExp, largerExp); Thank you!Trilly

© 2022 - 2024 — McMap. All rights reserved.