0
votes

I would like to create repository pattern class which gets items by query. Unfortunately I need to parse this query from one class to another (Picture to ListItem) to send it to a server(api). So my code should looks like below:

    public static void ConvertQuery(Expression<Func<Picture, object>> oldQuery)
    {
        Expression<Func<ListItem, object>> newQuery = convert(oldQuery);
    }

And, for example, i want to convert old query by cast properties like below:

  • SomePicture.Id => SomeListItem.Id
  • SomePicture.FileName => SomeListItem["FileName"]

I found some solutions where I can cast properties. But the biggest problem is with casting one property to dictionary field (item1.Filename to item2.["Filename"])

Update

@nejcs

I've tried to use you solution but unfortunately I have exception:

System.ArgumentException: 'ParameterExpression of type 'Microsoft.SharePoint.Client.ListItem' cannot be used for delegate parameter of type 'CastExpression.Picture''

The property "Item" is responsible for dictionary values, however I think that there is a problem with conversion. Below is stackTrace:

at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection 1 parameters) at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable 1 parameters) at System.Linq.Expressions.Expression 1.Update(Expression body, IEnumerable`1 parameters) at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression 1 node) at System.Linq.Expressions.Expression 1.Accept(ExpressionVisitor visitor) at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at CastExpression.Program.Main(String[] args)

I also know how Expression body looks like

For oldClassQuery:

Expression<Func<Picture, object>> oldQuery = x => x.FileName == "AS";

{x => Convert((x.FileName == "AS"))}

For newClassQuery:

Expression<Func<ListItem, object>> newQuery = x => x["FileName"] == "AS";

{x => Convert((x.get_Item("FileName") == "AS"))}

1
The solution for you is to use ExpressionVisitor and rewrite the expression tree manually. - Andrey Nasonov
I updated the answer regarding the exception that you received. - nejcs

1 Answers

0
votes

You are looking for ExpressionVisitor. Just create custom one by extending this class and override appropriate methods, which will transform subexpression from one form to another.

For example for transforming member access you would do something like this (by no means complete):

public class RewritingVisitor : ExpressionVisitor
{
    private readonly ParameterExpression p = Expression.Parameter(typeof(ListItem)); // create new parameter which will be referenced later

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(Picture))
        {
            return p;
        }
        return node;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var rewritten = Visit(node.Expression);
        if (rewritten == node.Expression) return node;

        if (node.Expression != null &&
            node.Expression.Type == typeof(Picture) &&
            rewritten.Type == typeof(ListItem))
        {
            if (node.Member.Name == "Id")
            {
                return Expression.MakeMemberAccess(
                    rewritten,
                    typeof(ListItem).GetProperty("Id"));
            }
            else if (node.Member.Name == "FileName")
            {
                return Expression.MakeIndex(
                    rewritten,
                    typeof(ListItem).GetProperty("Item"), // default indexer name
                    new[] { Expression.Constant("FileName") });
            }
        }
    }
}

You can then use it by simply instantiating it and call Visit method with lambda expression as argument:

var visitor = new RewritingVisitor();
var newQuery = visitor.Visit(oldQuery);

EDIT:

I forgot one small but fairly important piece: if child expressions are updated, by default visitor will call update (or similar method) on expression passing in new values. In case of lambda expression, validation logic expects expressions of the same types as were originally which of course is not true. You have to manually construct new lambda expression out of visited parts:

protected override Expression VisitLambda<T>(Expression<T> node)
{
    var lambdaExpr = (LambdaExpression)node;
    var rewrittenParameters = lambdaExpr.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray();
    var rewrittenBody = Visit(lambdaExpr.Body);

    return Expression.Lambda(rewrittenBody, rewrittenParameters);
}

This is missing override in your visitor, which takes care of creating new lambda from rewritten arguments and lambda body.