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:
And the response includes really all of the properties…
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):
- ClientContext ctx = new ClientContext("http://sp2013");
- var list = ctx.Web.Lists.GetByTitle("Images");
- var fieldsQuery = list.Fields.Include(f => f.InternalName, f => f.Id);
- // var fieldsQuery = list.Fields;
- var fields = ctx.LoadQuery(fieldsQuery);
- 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:
- ClientContext clientContext = new ClientContext("http://sp2013");
- IQueryable<Field> clientObjects = ClientObjectQueryableExtension.Include<Field>((IQueryable<Field>)clientContext.Web.Lists.GetByTitle("Images").Fields, new Expression<Func<Field, object>>[2]
- {
- (Expression<Func<Field, object>>) (f => f.InternalName),
- (Expression<Func<Field, object>>) (f => (object) f.Id)
- });
- clientContext.LoadQuery<Field>(clientObjects);
- 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:
- ClientContext ctx = new ClientContext("http://sp2013");
- var list = ctx.Web.Lists.GetByTitle("Images");
- // query the InternalName propery of type System.String
- var param1 = Expression.Parameter(typeof(Field), "f");
- var name1 = Expression.Property(param1, "InternalName");
- var expression1 = Expression.Lambda<Func<Field, object>>(name1, param1);
- // query the Id of type System.Guid
- var param2 = Expression.Parameter(typeof(Field), "f");
- var name2 = Expression.Property(param2, "Id");
- // convert the Guid type to object
- var body2 = Expression.Convert(name2, typeof(object));
- var expression2 = Expression.Lambda<Func<Field, object>>(body2, param2);
- // call Include with both of the expressions
- var fieldsQuery = ClientObjectQueryableExtension.Include<Field>(list.Fields, expression1, expression2);
- var fields = ctx.LoadQuery(fieldsQuery);
- ctx.ExecuteQuery();
The PowerShell version of the same functionality:
- $url = "http://sp2013"
- # load the required client object model assemblies
- Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
- Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
- $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
- $list = $ctx.Web.Lists.GetByTitle("Images")
- $expressionType = [System.Linq.Expressions.Expression]
- $parameterExpressionArrayType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()
- $lambdaMethod = $expressionType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2 -and $_.GetParameters()[1].ParameterType -eq $parameterExpressionArrayType }
- $lambdaMethodGeneric = $lambdaMethod.MakeGenericMethod([System.Func``2[Microsoft.SharePoint.Client.Field,System.Object]])
- $fieldType = [Microsoft.SharePoint.Client.Field]
- # query the InternalName propery of type System.String
- $param1 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
- $name1 = [System.Linq.Expressions.Expression]::Property($param1, "InternalName")
- $expression1 = $lambdaMethodGeneric.Invoke($Null, [System.Object[]] @($name1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))
- # query the Id of type System.Guid
- $param2 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
- $name2 = [System.Linq.Expressions.Expression]::Property($param2, "Id")
- # convert the Guid type to object
- $body2 = [System.Linq.Expressions.Expression]::Convert($name2, [System.Object])
- $expression2 = $lambdaMethodGeneric.Invoke($Null, [System.Object[]] @($body2, [System.Linq.Expressions.ParameterExpression[]] @($param2)))
- $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")
- $includeMethodGeneric = $includeMethod.MakeGenericMethod([Microsoft.SharePoint.Client.Field])
- # call Include with both of the expressions
- $fieldsQuery = $includeMethodGeneric.Invoke($Null, [System.Object[]] @($list.Fields, [System.Linq.Expressions.Expression`1[System.Func``2[Microsoft.SharePoint.Client.Field,System.Object]][]] @($expression1, $expression2)))
- $fields = $ctx.LoadQuery($fieldsQuery)
- $ctx.ExecuteQuery()
- $fields | % { Write-Host $_.InternalName: $_.Id }
Testing the script we found in Fiddler, that the request is limited to the properties we need:
and the response contains only the requested properties as well:
The output of the script should be similar to this one:
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:
- $url = "http://sp2013"
- # load the required client object model assemblies
- Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
- Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
- $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
- $list = $ctx.Web.Lists.GetByTitle("Images")
- $expressionType = [System.Linq.Expressions.Expression]
- $parameterExpressionArrayType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()
- $lambdaMethod = $expressionType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2 -and $_.GetParameters()[1].ParameterType -eq $parameterExpressionArrayType }
- $fieldType = [Microsoft.SharePoint.Client.Field]
- # call the Where method first
- $lambdaMethodGenericBool = $lambdaMethod.MakeGenericMethod([System.Func``2[Microsoft.SharePoint.Client.Field,System.Boolean]])
- $param = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
- $name = [System.Linq.Expressions.Expression]::PropertyOrField($param, "TypeAsString")
- $body = [System.Linq.Expressions.Expression]::Equal($name, [System.Linq.Expressions.Expression]::Constant("Guid"))
- $expression = $lambdaMethodGenericBool.Invoke($Null, [System.Object[]] @($body, [System.Linq.Expressions.ParameterExpression[]] @($param)))
- $whereMethod = [System.Linq.Queryable].GetMethods() | ? { $_.Name -eq "Where" -and $_.ToString() -like "*TSource,System.Boolean*" }
- $whereMethodGeneric = $whereMethod.MakeGenericMethod([Microsoft.SharePoint.Client.Field])
- $fieldsQuery = $whereMethodGeneric.Invoke($Null, [System.Object[]] @($list.Fields, $expression))
- # call the Include method next on the result of the Where method
- $lambdaMethodGenericObj = $lambdaMethod.MakeGenericMethod([System.Func``2[Microsoft.SharePoint.Client.Field,System.Object]])
- # query the InternalName propery of type System.String
- $param1 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
- $name1 = [System.Linq.Expressions.Expression]::Property($param1, "InternalName")
- $expression1 = $lambdaMethodGenericObj.Invoke($Null, [System.Object[]] @($name1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))
- # query the Id of type System.Guid
- $param2 = [System.Linq.Expressions.Expression]::Parameter($fieldType, "f")
- $name2 = [System.Linq.Expressions.Expression]::Property($param2, "Id")
- # convert the Guid type to object
- $body2 = [System.Linq.Expressions.Expression]::Convert($name2, [System.Object])
- $expression2 = $lambdaMethodGenericObj.Invoke($Null, [System.Object[]] @($body2, [System.Linq.Expressions.ParameterExpression[]] @($param2)))
- $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")
- $includeMethodGeneric = $includeMethod.MakeGenericMethod([Microsoft.SharePoint.Client.Field])
- # call Include with both of the expressions
- $fieldsQuery2 = $includeMethodGeneric.Invoke($Null, [System.Object[]] @($fieldsQuery, [System.Linq.Expressions.Expression`1[System.Func``2[Microsoft.SharePoint.Client.Field,System.Object]][]] @($expression1, $expression2)))
- $fields = $ctx.LoadQuery($fieldsQuery2)
- $ctx.ExecuteQuery()
- $fields | % { Write-Host $_.InternalName: $_.Id }
When checking the network traffic using Fiddler we found that both the request
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: