Second Life of a Hungarian SharePoint Geek

June 15, 2014

How to restrict the properties in the results of Client Object Model requests sent from PowerShell on the server side, implementing the Include method

Filed under: Managed Client OM, PowerShell, Reflection, SP 2013 — Tags: , , , — Peter Holpar @ 22:51

In my recent post I illustrated how one can limit the entities returned by the server when responding a Client Object Model request sent from PowerShell. Although that method restricts the entities to the ones we really need, it still returns all of the properties for those items.

See the SelectAllProperties = true attribute of the ChildItemQuery in the request captured by Fiddler:

image

And the response includes really all of the properties…

image

However, we need typically only a few properties, the others only increase the size of the response package sent by the server unnecessarily.

In the C# syntax we can limit the fields using the Include method (see Retrieving only specified properties of lists):

  1. ClientContext ctx = new ClientContext("http://sp2013");
  2. var list = ctx.Web.Lists.GetByTitle("Images");
  3. var fieldsQuery = list.Fields.Include(f => f.InternalName, f => f.Id);
  4. // var fieldsQuery = list.Fields;
  5. var fields = ctx.LoadQuery(fieldsQuery);
  6. ctx.ExecuteQuery();

Remark: The JavaScript version supports a kind of Include as well (see Retrieving Only Specified Properties of Lists Using JavaScript).

Following the same strategy as in the case of the Where method, we compile our code to an assembly, and decompile it using JetBrains dotPeek:

  1. ClientContext clientContext = new ClientContext("http://sp2013");
  2. IQueryable<Field> clientObjects = ClientObjectQueryableExtension.Include<Field>((IQueryable<Field>)clientContext.Web.Lists.GetByTitle("Images").Fields, new Expression<Func<Field, object>>[2]
  3.   {
  4.     (Expression<Func<Field, object>>) (f => f.InternalName),
  5.     (Expression<Func<Field, object>>) (f => (object) f.Id)
  6.   });
  7. clientContext.LoadQuery<Field>(clientObjects);
  8. clientContext.ExecuteQuery();

Decompiling the code with Reflector reveals the internals of the lambda expression query:

IQueryable<Field> fieldsQuery = ctx.Web.Lists.GetByTitle("Images").Fields.Include<Field>(new Expression<Func<Field, object>>[] { Expression.Lambda<Func<Field, object>>(Expression.Property(CS$0$0001 = Expression.Parameter(typeof(Field), "f"), (MethodInfo) methodof(Field.get_InternalName)), new ParameterExpression[] { CS$0$0001 }), Expression.Lambda<Func<Field, object>>(Expression.Convert(Expression.Property(CS$0$0001 = Expression.Parameter(typeof(Field), "f"), (MethodInfo) methodof(Field.get_Id)), typeof(object)), new ParameterExpression[] { CS$0$0001 }) });

Transferring this quite complicate expression to a bit more readable format our method looks like this:

  1. ClientContext ctx = new ClientContext("http://sp2013&quot;);
  2. var list = ctx.Web.Lists.GetByTitle("Images");
  3.  
  4. // query the InternalName propery of type System.String
  5. var param1 = Expression.Parameter(typeof(Field), "f");
  6. var name1 = Expression.Property(param1, "InternalName");
  7.  
  8. var expression1 = Expression.Lambda<Func<Field, object>>(name1, param1);
  9.  
  10. // query the Id of type System.Guid
  11. var param2 = Expression.Parameter(typeof(Field), "f");
  12. var name2 = Expression.Property(param2, "Id");
  13. // convert the Guid type to object
  14. var body2 = Expression.Convert(name2, typeof(object));
  15.  
  16. var expression2 = Expression.Lambda<Func<Field, object>>(body2, param2);
  17.  
  18. // call Include with both of the expressions
  19. var fieldsQuery = ClientObjectQueryableExtension.Include<Field>(list.Fields, expression1, expression2);
  20.  
  21. var fields = ctx.LoadQuery(fieldsQuery);
  22. ctx.ExecuteQuery();

The PowerShell version of the same functionality:

  1. $url = "http://sp2013&quot;
  2.  
  3. # load the required client object model assemblies
  4. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
  5. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
  6.  
  7. $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
  8. $list = $ctx.Web.Lists.GetByTitle("Images")
  9.  
  10. $expressionType = [System.Linq.Expressions.Expression]
  11. $parameterExpressionArrayType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()
  12.  
  13. $lambdaMethod = $expressionType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2  -and $_.GetParameters()[1].ParameterType -eq $parameterExpressionArrayType }
  14. $lambdaMethodGeneric = $lambdaMethod.MakeGenericMethod([System.Func“2[Microsoft.SharePoint.Client.Field,System.Object]])
  15.  
  16. $fieldType = [Microsoft.SharePoint.Client.Field]
  17.  
  18. # query the InternalName propery of type System.String
  19. $param1 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
  20. $name1 = [System.Linq.Expressions.Expression]::Property($param1, "InternalName")
  21. $expression1 = $lambdaMethodGeneric.Invoke($Null, [System.Object[]] @($name1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))
  22.  
  23. # query the Id of type System.Guid
  24. $param2 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
  25. $name2 = [System.Linq.Expressions.Expression]::Property($param2, "Id")
  26.  
  27. # convert the Guid type to object
  28. $body2 = [System.Linq.Expressions.Expression]::Convert($name2, [System.Object])
  29.  
  30. $expression2 = $lambdaMethodGeneric.Invoke($Null, [System.Object[]] @($body2, [System.Linq.Expressions.ParameterExpression[]] @($param2)))
  31.  
  32. $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")
  33. $includeMethodGeneric = $includeMethod.MakeGenericMethod([Microsoft.SharePoint.Client.Field])
  34.  
  35. # call Include with both of the expressions
  36. $fieldsQuery = $includeMethodGeneric.Invoke($Null, [System.Object[]] @($list.Fields, [System.Linq.Expressions.Expression`1[System.Func“2[Microsoft.SharePoint.Client.Field,System.Object]][]] @($expression1, $expression2)))
  37.  
  38. $fields = $ctx.LoadQuery($fieldsQuery)
  39. $ctx.ExecuteQuery()
  40.  
  41. $fields | % { Write-Host $_.InternalName: $_.Id }

Testing the script we found in Fiddler, that the request is limited to the properties we need:

image

and the response contains only the requested properties as well:

image

The output of the script should be similar to this one:

image

Note: In the case above, the child items are not restricted via a QueryableExpression, however you can combine the Where method described in the former post and the Include method to restrict both the child entities and their properties returned by the server:

fields.Where(f => f.TypeAsString == "Guid").Include(f => f.Id, f => f.InternalName)

In the PowerShell equivalent we chain our expression to achieve the same result:

  1. $url = "http://sp2013&quot;
  2.  
  3. # load the required client object model assemblies
  4. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
  5. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
  6.  
  7. $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
  8. $list = $ctx.Web.Lists.GetByTitle("Images")
  9.  
  10. $expressionType = [System.Linq.Expressions.Expression]
  11. $parameterExpressionArrayType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()
  12.  
  13. $lambdaMethod = $expressionType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2  -and $_.GetParameters()[1].ParameterType -eq $parameterExpressionArrayType }
  14.  
  15. $fieldType = [Microsoft.SharePoint.Client.Field]
  16.  
  17. # call the Where method first
  18. $lambdaMethodGenericBool = $lambdaMethod.MakeGenericMethod([System.Func“2[Microsoft.SharePoint.Client.Field,System.Boolean]])
  19.  
  20. $param = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
  21. $name = [System.Linq.Expressions.Expression]::PropertyOrField($param, "TypeAsString")
  22. $body = [System.Linq.Expressions.Expression]::Equal($name, [System.Linq.Expressions.Expression]::Constant("Guid"))
  23. $expression = $lambdaMethodGenericBool.Invoke($Null, [System.Object[]] @($body, [System.Linq.Expressions.ParameterExpression[]] @($param)))
  24.  
  25. $whereMethod = [System.Linq.Queryable].GetMethods() | ? { $_.Name -eq "Where" -and $_.ToString() -like "*TSource,System.Boolean*" }
  26. $whereMethodGeneric = $whereMethod.MakeGenericMethod([Microsoft.SharePoint.Client.Field])
  27.  
  28. $fieldsQuery = $whereMethodGeneric.Invoke($Null, [System.Object[]] @($list.Fields, $expression))
  29.  
  30. # call the Include method next on the result of the Where method
  31. $lambdaMethodGenericObj = $lambdaMethod.MakeGenericMethod([System.Func“2[Microsoft.SharePoint.Client.Field,System.Object]])
  32.  
  33. # query the InternalName propery of type System.String
  34. $param1 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
  35. $name1 = [System.Linq.Expressions.Expression]::Property($param1, "InternalName")
  36. $expression1 = $lambdaMethodGenericObj.Invoke($Null, [System.Object[]] @($name1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))
  37.  
  38. # query the Id of type System.Guid
  39. $param2 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
  40. $name2 = [System.Linq.Expressions.Expression]::Property($param2, "Id")
  41.  
  42. # convert the Guid type to object
  43. $body2 = [System.Linq.Expressions.Expression]::Convert($name2, [System.Object])
  44.  
  45. $expression2 = $lambdaMethodGenericObj.Invoke($Null, [System.Object[]] @($body2, [System.Linq.Expressions.ParameterExpression[]] @($param2)))
  46.  
  47. $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")
  48. $includeMethodGeneric = $includeMethod.MakeGenericMethod([Microsoft.SharePoint.Client.Field])
  49.  
  50. # call Include with both of the expressions
  51. $fieldsQuery2 = $includeMethodGeneric.Invoke($Null, [System.Object[]] @($fieldsQuery, [System.Linq.Expressions.Expression`1[System.Func“2[Microsoft.SharePoint.Client.Field,System.Object]][]] @($expression1, $expression2)))
  52.  
  53. $fields = $ctx.LoadQuery($fieldsQuery2)
  54. $ctx.ExecuteQuery()
  55.  
  56. $fields | % { Write-Host $_.InternalName: $_.Id }

When checking the network traffic using Fiddler we found that both the request

image

and the response fulfills our expectations.

The output of the script contains only the two returned fields due to the filtering on the server side:

image

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: