Paged Collection in Data Service

It is naturally to use Expand in a Linq query to get children collection under an entity, as showed in the following example.

using System.Collections.Generic;
using System.Linq;

public IEnumerable<User> GetMembersByGroup(string groupIdentifier)
{
  var team = this.dataContext
      // each Team has a MemberUsers collection
      .Teams.Expand(t => t.MemberUsers)
      .Where(t => t.Id == groupIdentifier)
      .Single();

  return team.MemberUsers.ToList();
}

The above code would work as long as the size of the collection is small and within server paging size of the data service (- see this blog). In order to get result from all paged collection, the following example is using DataServiceCollection<T>.Load method.

Note: The service reference needs have UseDataServiceCollection enabled in .datasvcmap configuration which should be supported by .NET 3.5 SP1 and 4. In other case, rather than System.Data.Services.Client.DataServiceCollection, the System.Collections.ObjectModel.Collection won't have Continuation.
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;

public IEnumerable<User> GetMembersByGroup(string groupIdentifier)
{
  var team = this.dataContext
      // expand both MemberUsers and Children teams
      .Teams.Expand("MemberUsers,Children/MemberUsers")
      .Where(t => t.Id == groupIdentifier)
      .AsEnumerable()
      .FirstOrDefault();

  if (team == null)
  {
    return new List<User>();
  }

  DataServiceCollection<User> users = team.MemberUsers;

  while (users.Continuation!= null)
  {
    users.Load(this.dataContext.Execute(users.Continuation));
  }

  return users;
}

The DataServiceCollection<T> requires a type T. For Query Projction with anonymous (or Tuple) type, the query can load data but may not support Continuation.


Another similar solution is sending data query to data service with GetContinuation.
using System.Collections.Generic;
using System.Linq;

public IEnumerable<User> GetMembersByGroup(string groupIdentifier)
{
  var dataQuery = 
        from t in this.dataContext.Teams
       where t.Id == groupIdentifier
        from u in t.MemberUsers
      select u;

  var users = this.dataContext.GetAll(dataQuery);

  return users;
}

Since only navigation query supports join operation, the query must be on the primary key (as the Id in above code); otherwise, use another query with SingleOrDefault or FirstOrDefault in prior to get the identifier. Also, GetAll method should support Query Projection, so that GetAll(dataQuery) can be used, instead of GetAll((DataServiceQuery)dataQuery).

In order to query all users by a name (which is not a navigation query), the following data query projects the result to a collection of an anonymous typed objects, so that we can get identifiers for navigation queries later.
using System.Collections.Generic;
using System.Linq;

public IEnumerable<string> GetUsersByName(string userName)
{
  var dataQuery = 
        from u in this.Users
       where u.Name == userName
      select new {
      {
        Id = u.Id, Alias = u.Alias
      }
  var result = this.dataContext.GetAll(dataQuery);
  var users = result.Select(a => a.Id);

  return users;
}

Here is the source of GetAll extension (with support of Query Projection):
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;

public static IEnumerable<T> GetAll<T>(this
  DataServiceContext dataContext, 
  IQueryable<T> dataServiceQuery)
{
  QueryOperationResponse<T> response = 
    (QueryOperationResponse<T>)
    ((DataServiceQuery)dataServiceQuery).Execute();

  DataServiceQueryContinuation<T> continuation = null;

  do
  {
    if (continuation != null)
    {
      response = this.dataContext.Execute(continuation);
    }

    foreach (var result in response)
    {
      yield return result;
    }

    continuation = response.GetContinuation();
  }
  while (continuation != null);
}