Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a function that returns an expression to be called rather than interpreted as an expression

I'm working with SharePoint CSOM. This uses a query language based on expressions to specify what data to retrieve. The system I am working on makes several different queries, but they all need to include certain data for security checking. For example:

using (ClientContext context = new ClientContext(weburl))
{
    var subwebs = context.Web.GetSubwebsForCurrentUser(new SubwebQuery());
    context.Load(subwebs,
        webs => webs.Include(
            // BEGIN security-related boilerplate
            web => web.RoleAssignments.Include(
                ra => ra.Member,
                ra => ra.RoleDefinitionBindings.Include(
                    rdb => rdb.BasePermissions,
                    rdb => rdb.RoleTypeKind
                )
            ),
            // END security-related boilerplate
            web => web.Title,
            web => web.ServerRelativeUrl));

    context.ExecuteQuery();
}

I'm trying to avoid having to copy/paste the security-related expressions every place that they are needed. Examination of the Include method shows that it takes a list of expressions as arguments:

IQueryable<TSource> Include<TSource>(
    this IQueryable<TSource> clientObjects, 
    params Expression<Func<TSource, object>>[] retrievals) 
    where TSource : ClientObject

So I created an extension method that returns the required expression:

public static Expression<Func<T, object>> GetSecurityExpression<T>(this ClientObjectCollection<T> list) 
    where T : SecurableObject
{
    return x => x.RoleAssignments.Include(
        ra => ra.Member,
        ra => ra.RoleDefinitionBindings.Include(
            rdb => rdb.BasePermissions,
            rdb => rdb.RoleTypeKind
        )
    );
}

And the query is rewritten like this:

using (ClientContext context = new ClientContext(weburl))
{
    var subwebs = context.Web.GetSubwebsForCurrentUser(new SubwebQuery());
    context.Load(subwebs,
        webs => webs.Include(
            subwebs.GetSecurityExpression(),
            web => web.Title,
            web => web.ServerRelativeUrl));

    context.ExecuteQuery();
}

This compiles fine, but at runtime it throws a Microsoft.SharePoint.Client.InvalidQueryExpressionException:

The query expression 'value(Microsoft.SharePoint.Client.WebCollection).GetSecurityExpression()' is not supported.

If I understand the exception correctly, the compiler is not calling subwebs.GetSecurityExpression(), but is creating an expression from it and passing that to Include.

The workaround I've found is to write the calling code like this:

using (ClientContext context = new ClientContext(weburl))
{
    var subwebs = context.Web.GetSubwebsForCurrentUser(new SubwebQuery());
    var securityExpression = subwebs.GetSecurityExpression();
    context.Load(subwebs,
        webs => webs.Include(
            securityExpression,
            web => web.Title,
            web => web.ServerRelativeUrl));

    context.ExecuteQuery();
}

This works, but is not as succinct as I'd like it to be. Is there any way to get the first approach to work?

like image 236
Jack A. Avatar asked Dec 20 '25 00:12

Jack A.


1 Answers

The problem you face is that your method GetSecurityExpression will become part of the expression tree in the first variant, and SharePoint is not able to parse this bit. In the second variant, the method call is evaluated before the creation of the expression tree and the actual expression tree will contain the correct sub-tree (i.e. what you write inside the GetSecurityExpression method).

In principle it is possible to evaluate parts of an expression tree before executing the query. Have a look at ReLinq; it has a class PartialEvaluatingExpressionTreeVisitor which does exactly this job. However, this will come at a small performance penalty.

like image 91
Roland Buergi Avatar answered Dec 22 '25 19:12

Roland Buergi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!