As you saw in the preceding code snippets,
although the Client Object Model defines client-side representations of
server objects and we can create instances of those objects in our
code, the objects are not populated with a copy of the server-side data
until it is explicitly loaded.
In-place Load
This Silverlight code snippet shows how to execute a Collaborative Application Markup Language (CAML) query against a list:
private void CAMLQUery_Click(object sender, RoutedEventArgs e)
{
ClientContext ctx = new ClientContext("Your site here");
CamlQuery query = new CamlQuery();
query.ViewXml = "<View><Query><OrderBy>
<FieldRef Name=\"Editor\" Ascending=\"False\" />
</OrderBy></Query></View>";
List announcements = ctx.Web.Lists.GetByTitle("Announcements");
FieldCollection fields = announcements.Fields;
ctx.Load(fields);
ListItemCollection listItems = announcements.GetItems(query);
ctx.Load(listItems);
ctx.ExecuteQueryAsync((s, args) =>
{
Dispatcher.BeginInvoke(() =>
{
BuildTable(fields, listItems);
});
}, (s, args) =>
{
Dispatcher.BeginInvoke(() =>
{
label1.Content = args.Message;
});
});
}
To perform the same function using JavaScript, you can use this:
function CAMLQuery_Click() {
var ctx = new SP.ClientContext.get_current();
var query = new SP.CamlQuery();
query.viewXml = "<View><Query><OrderBy>
<FieldRef Name=\"Editor\" Ascending=\"False\" />
</OrderBy></Query></View>";
var announcements = ctx.get_web().get_lists().getByTitle("Announcements");
var listItems = announcements.getItems(query);
ctx.load(listItems);
var fields = announcements.get_fields();
ctx.load(fields);
ctx.executeQueryAsync(function (s, args) {
var console = document.getElementById('DemoConsole');
console.innerHTML = buildTable(fields,listItems);
}, null);
}
The important thing to note about these code samples
is the use of the ClientContext.Load method. This method flags the
passed in object to be populated the next time ExecuteQueryAsync is
called, so in the preceding example, ctx.Load(listItems) will flag the listItems object for population.
To use these code snippets with the demo project
that we set up earlier, take the following steps—first, for the
Silverlight sample:
Add a new button labeled Execute CAML Query to MainPage.xaml, and set the Click event handler to CAMLQuery_Click.
Add the Silverlight code snippet listed above into MainPage.xaml.cs.
Add a ScrollViewer control to MainPage.xaml and then, inside the ScrollViewer, add a Grid control named grid1.
Add the following code into MainPage.xaml.cs:
void BuildTable(FieldCollection fields, ListItemCollection listItems)
{
grid1.RowDefinitions.Clear();
grid1.ColumnDefinitions.Clear();
grid1.ShowGridLines = false;
grid1.RowDefinitions.Add(new RowDefinition {
Height=new GridLength(0,GridUnitType.Auto)});
int i = 0;
foreach (var field in fields)
{
if (!field.Hidden)
{
grid1.ColumnDefinitions.Add(new ColumnDefinition {
Width=new GridLength(0,GridUnitType.Auto)
});
TextBlock label = new TextBlock
{
Text = field.Title,
HorizontalAlignment = HorizontalAlignment.Center,
FontWeight = FontWeights.Bold,
Margin = new Thickness(10, 0, 10, 0),
};
label.SetValue(Grid.RowProperty, 0);
label.SetValue(Grid.ColumnProperty, i);
grid1.Children.Add(label);
i++;
}
}
int row = 1;
foreach (var item in listItems)
{
i = 0;
grid1.RowDefinitions.Add(new RowDefinition {
Height=new GridLength(0,GridUnitType.Auto)});
foreach (var field in fields)
{
if (!field.Hidden)
{
TextBlock label = new TextBlock {
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(10, 0, 10, 0),
};
try
{
label.Text = item[field.StaticName].ToString();
}
catch (Exception)
{
label.Text = "--";
}
label.SetValue(Grid.RowProperty, row);
label.SetValue(Grid.ColumnProperty,i);
i++;
grid1.Children.Add(label);
}
}
row++;
}
}
Build the SilverlightCOMDemo project.
Now, for the JavaScript sample, do the following:
Using SharePoint Designer, modify JavascriptTest.aspx to include an additional button.
Above the DemoConsole div tag that we added earlier, insert the following markup:
<input name="Button2" type="button" value="Execute CAML Query"
onclick="CAMLQuery_Click()"></input>
Within
the JScriptTest.js file in our SharePoint project, add the
CAMLQuery_Click JavaScript sample method listed above and then add the
following function:
function buildTable(fields,listItems) {
var output = "";
output = "<table><thead style=\"font-weight:bold\"><tr>";
var fieldEnum = fields.getEnumerator();
while (fieldEnum.moveNext()) {
var field = fieldEnum.get_current();
if (field.get_hidden() != true) {
output += "<td>" + field.get_title() + "</td>";
}
}
output += "</tr></thead>";
var enumerator = listItems.getEnumerator();
while (enumerator.moveNext()) {
var item = enumerator.get_current();
fieldEnum.reset();
output += "<tr>";
while (fieldEnum.moveNext()) {
var field = fieldEnum.get_current();
if (field.get_hidden() != true) {
try {
output += "<td>" + item.get_item(field.get_staticName()) + "</td>";
} catch (e) {
output += "<td>--</td>";
}
}
}
output += "</tr>";
}
output += "</table>"
return output;
}
Object Identity
Although property values are not populated until
they are explicitly loaded, it is still possible to make use of the
properties in expressions, as long as the expressions themselves are
not enumerated until after the properties are loaded. In our code
sample, we can see an example of this in the following line:
List announcements = ctx.Web.Lists.GetByTitle("Announcements");
If we were to attempt to enumerate any of the
properties of the announcements object, an error would be thrown since
the value has not been initialized. The same is also true of the
properties of the object referred to by the ctx.Web property. We can
use the property in an expression because an expression uses a
reference to the property rather than its actual value.
You can see that our code sample makes use of the
announcements object in various places, but we never explicitly
populate it. Loading an object is necessary only if its properties will
be converted to a value.
One easy way to look at it is to consider that each
object is a proxy for an appropriate server-side object. Using the
proxy, we can perform many of the same actions, but until we explicitly
load data from the real object into the proxy, we can’t use the data on
the client side because it exists only on the server.
Filtering Returned Data
When we execute the preceding code samples, a table
is generated containing the field values that are returned for each
list item, as shown:
This works well and doesn’t present much of a
problem when only a few items are included in the list or library. But
what happens if tens of thousands of items are included and we need
only one or two columns? Transferring all this redundant data to the
client would have a major performance impact.
Thankfully, we can eliminate redundant data by
filtering the properties that are populated by the Load method, as the
following snippets show. To see the results of these samples, create a
new button and hook up the Click event to the sample code, as we’ve
done in the past few samples.
Here’s the snippet in Silverlight:
private void FilterQuery_Click(object sender, RoutedEventArgs e)
{
ClientContext ctx = new ClientContext("Your site here");
CamlQuery query = new CamlQuery();
query.ViewXml = "<View><Query><OrderBy>
<FieldRef Name=\"Editor\" Ascending=\"False\" />
</OrderBy></Query></View>";
List announcements = ctx.Web.Lists.GetByTitle("Announcements");
FieldCollection fields = announcements.Fields;
ctx.Load(fields,
fs => fs.Include(
f => f.Hidden,
f => f.Title,
f => f.StaticName).Where(f => f.Hidden == false)
);
ListItemCollection listItems = announcements.GetItems(query);
ctx.Load(listItems,
items => items.Include(
item => item["Title"]
));
ctx.ExecuteQueryAsync((s, args) =>
{
Dispatcher.BeginInvoke(() =>
{
BuildTable(fields, listItems);
});
}, (s, args) =>
{
Dispatcher.BeginInvoke(() =>
{
label1.Content = args.Message;
});
});
}
And here’s the JavaScript:
function FilteredQuery_Click() {
var ctx = new SP.ClientContext.get_current();
var query = new SP.CamlQuery();
query.viewXml = "<View><Query><OrderBy>
<FieldRef Name=\"Editor\" Ascending=\"False\" />
</OrderBy></Query></View>";
var announcements = ctx.get_web().get_lists().getByTitle("Announcements");
var listItems = announcements.getItems(query);
ctx.load(listItems,"Include(Title)");
var fields = announcements.get_fields();
ctx.load(fields,"Include(Hidden,Title,StaticName)");
ctx.executeQueryAsync(function (s, args) {
var console = document.getElementById('DemoConsole');
console.innerHTML = buildTable(fields, listItems);
}, null);
}
In the Silverlight sample, we’re making use of
lambda expressions and Language Integrated Query (LINQ) to filter the
returned data. Because LINQ syntax isn’t supported by
JavaScript, we’re using a string containing a filter expression. When
creating JavaScript filter strings, LINQ operations such as Where are
not supported.
You can see that by running these samples, the
resulting table contains only the Title column. Although you can’t see
it from the output, the population of the fields collection has been
filtered to include only the properties that are required for the
logic. The result of these changes is that the data exchanged between
client and server is greatly reduced.
Note
When querying lists or libraries, you need to be
aware of how filtering is applied behind the scenes. First, the CAML
query object passed into the GetItems method is used to retrieve a list
of items into memory. With the item list in memory, LINQ to Objects is
used to apply the filters that we’re defining with the Load method.
This is significant, because when you’re querying large lists,
item-level filtering should always be done using CAML to reduce the
memory usage on the server.
Queryable Load
In
addition to the ClientContext.Load method used in the preceding
examples, the Client Object Model also provides a
ClientContext.LoadQuery method. The difference between these two
methods is in the object that receives the results of the method. When
calling Load, we’ll pass in a reference to the object that we want to
load along with any filtering expression; when the method has executed,
that object that we passed in is loaded with the appropriate data. With
LoadQuery, when the method has executed, an IEnumerable(T) collection
is returned.
You may be wondering about the benefits of such a
subtle difference between these methods. The main benefit is that
LoadQuery can accept a LINQ query as a parameter; also, the results
returned can be further processed using LINQ.
In the preceding Silverlight example, we could replace this
FieldCollection fields = announcements.Fields;
ctx.Load(fields,
fs => fs.Include(
f => f.Hidden,
f => f.Title,
f => f.StaticName).Where(f => f.Hidden == false)
);
with this:
var filteredFields = announcements.Fields.Include(
f => f.Hidden,
f => f.Title,
f => f.StaticName);
var fields = ctx.LoadQuery(from f in filteredFields
where f.Hidden == false
select f);
In the second example, the fields variable
contains an IEnumerable<Field> object as opposed to the
FieldCollection object that would be populated with the first example.
Although the JavaScript object model also
includes the loadQuery method, since JavaScript doesn’t support LINQ,
its primary function is to return the results as a separate variable
rather than populating the appropriate ClientContext property. Other
than that, there is no real benefit to using loadQuery in JavaScript.