Feeling a bit loopy

A looping construct is one that is often requested in the Live Framework forum discussions regarding scripting. With the latest release you can now perform loops and iterate over collections in resource scripts.

In the following example we will walk through building a function that creates a batch of data feeds within a mesh object. The same logic can be applied to data entries or contacts or whatever resource you like.

So what I would like to do is create a bunch of data feeds on the client then have them batch added to a mesh object. I want the method to look like this

 void BatchCreateDataFeedsWithLoop(MeshObject meshObject, List<DataFeed> feeds)
{ ... }

First, since resource scripts deal with resources instead of the .NET container objects we need to extract the list of resources from the list of data feeds. My LiveFXHelper class has a method to do this already.

 List<DataFeedResource> resources = LiveFXHelper.GetResourceList(feeds);

In order to do the loop we need to create a loop statement. We also need to pass in another statement which will be executed during each iteration – the body, and the collection over which the loop will iterate.

 LoopStatement loop = Statement.Loop(
    "loop",
    body,
    resources.Count,
    null,
    resources.ToArray()
    );

The body statement, which is going to be the loop execution, can be any sequence of resource script statements. In this case we will just issue a single statement during each iteration which will create the DataFeedResource. The main thing to note here is the binding to the “CurrentIterationData” property on the “loop” statement. This property will represent the current item of the collection that we are looping over. We bind that to the “Request” property of the current statement and it will be the basis for the CreateResource action we are scripting. We also need to pass in the type of the data in the collection that is being bound.

 Statement body = Statement.CreateResource<DataFeedResource>(
    "createDF", 
    meshObject.Resource.DataFeedsLink,
    null, 
    null, 
    true,
    Statement.Bind("Request", "loop", "CurrentIterationData", typeof(DataFeedResource))
    );

Once we have our scripts set up, we need to compile and execute them at the server using the token we originally used to log in the LOE.

 script.RunAtServer(LoeLogOnHelper.LiveIDUserToken, AuthenticationTokenType.UserToken, loe.Resource.ScriptLink);
if (script.Error != null)
{
    Console.WriteLine("Script failed: " + script.Error.ToString());
    break;
}

We also need to check the script.Error property in case there was a problem executing the script. The current CTP has a limit of 50 iterations per loop statement and if you exceed that, the script.Error property will be populated with an exception that indicates the problem.

If we add in a little logic to batch the original request into groups of 50, we get a nice little method which will create an arbitrary number of data feeds for us on the server and save a ton of bandwidth.

 static private void BatchCreateDataFeedsWithLoop(MeshObject meshObject, List<DataFeed> feeds)
{
    // pull out the resources from the data feeds
    List<DataFeedResource> resources = LiveFXHelper.GetResourceList(feeds);

    // need to break this into batches of 50 to prevent going over the server
    // iteration limit
    int batchSize = 50;
    for (int i = 0; i < resources.Count; i += batchSize)
    {
        int count = batchSize;
        if (i + batchSize > resources.Count)
        {
            count = resources.Count % batchSize;
        }
        List<DataFeedResource> currentBatch = resources.GetRange(i, count);

        // build the statement that does the creation. This is the statement we
        // will loop over
        Statement body = Statement.CreateResource<DataFeedResource>(
            "createDF",
            meshObject.Resource.DataFeedsLink,
            null,
            null,
            true,
            Statement.Bind("Request", "loop", "CurrentIterationData", typeof(DataFeedResource))
            );

        // build the parameter which references the resource array
        LoopStatement loop = Statement.Loop(
            "loop",
            body,
            currentBatch.Count,
            null,
            currentBatch.ToArray()
            );

        // compile and run the script
        ResourceScript<LoopStatement> script = loop.Compile();

        script.RunAtServer(LoeLogOnHelper.LiveIDUserToken, AuthenticationTokenType.UserToken, loe.Resource.ScriptLink);
        if (script.Error != null)
        {
            Console.WriteLine("Script failed: " + script.Error.ToString());
            break;
        }
    }
}