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

Paul Cavacas
  • 4,194
  • 5
  • 31
  • 60
  • 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 filtered = Queryable.Where(source, (dynamic)expression);` – Marc Gravell Aug 19 '13 at 19:02

1 Answers1

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);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • But the method that I need to call takes an Expression-Func not a dynamic, so I can't pass in the a dynamic. – Paul Cavacas Aug 19 '13 at 19:14
  • @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. – Marc Gravell Aug 19 '13 at 19:18
  • I nearly have it working. One remaining problem. Se question for further details. – Paul Cavacas Aug 19 '13 at 19:58
  • I get Instance property 'RelationshipNumber' is not defined for type IContract. RelationshipNumber is on the IRelationship interface – Paul Cavacas Aug 19 '13 at 20:05
  • @Paul then... you don't need to mention `IContract` *at all* - just `Expression.Property(parameter, data.SortColumn)` - passing `parameter` on the last line – Marc Gravell Aug 19 '13 at 20:15
  • 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 repository – Paul Cavacas Aug 19 '13 at 20:17
  • @Paul then you need to make the parameter of type IContract, and reverse the convert to get to IRelationship – Marc Gravell Aug 19 '13 at 20:19
  • 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); – Paul Cavacas Aug 19 '13 at 20:28
  • I got it. See question for final answer – Paul Cavacas Aug 19 '13 at 20:38
  • @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? – Jones Mar 09 '21 at 20:34
  • @Jones what do require to be the string `AND`? the `ToString()` representation? if that doesn't meet your needs, you'll have to implement it manually – Marc Gravell Mar 10 '21 at 12:49
  • @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! – Jones Mar 23 '21 at 09:15