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);
}


Paper Sizes

A0规格:1189*841mm
A1规格: 841*594mm
A2规格: 594*420mm
A3规格: 420*297mm
A4规格: 297*210mm
A5规格: 210*148mm

大度16开:211x290mm(全张:850x1168mm)
正度16开:195x271mm(全张:787x1092mm)

大度32开:140x203mm
正度32开:130x184mm
大32开:136x210mm
标准32开:127x184mm
小32开: 92x 85mm

EqualityComparer

In C#, for a list of string (or some primitive type), IEnumerable.Distinct() method can help reduce the duplicates.

var distinctList = myList.Distinct();

However, for a list of complex type, this may not work as expected, since Distinct() will produce a new list based on the hash code of each item. For any Foo type class, in order to use Distinct() method, Equals() and GetHashCode() need to be override.
public override bool Equals(object obj)
{
  Foo other = obj as Foo;
  return 
    other != null && 
    other.Name.Equals(this.Name) && 
    other.Prop.Equals(this.Prop);
}

public override int GetHashCode()
{
  var tuple = new Tuple<string, PropType>(this.Name, this.Prop);
  int hashCode = tuple.GetHashCode();
  return hashCode;
}
For hash code, see algorithms, here, and here. Do remember the rule: "If two objects compare as equal, the GetHashCode method for each object must return the same value. However, if two objects do not compare as equal, the GetHashCode methods for the two object do not have to return different values." (See more rules and guidelines on this blog)

In many cases, the developer may not own the code of class Foo to override Equals() and GetHashCode(), or the Distinct may vary in runtime. One solution is to use GroupBy method in System.Linq.Enumerable extension applying on item's property (e.g. using Name as a key to distinguish each item):
var distinctList = strList
    .GroupBy(i => i.Name)
    .Select(g => g.First())
    .ToList();

And for more than 1 key property (e.g. Prop1 and Prop2) in a group:
var distinctList = myList
    .GroupBy(i => new { i.Prop1, i.Prop2 })
    .Select(g => g.First())
    .ToList();

Same can be done with group operator using System.Linq:
var distinctList = (
    from i in recipients
    group i by new { i.Prop1, i.Prop2 } into grp
    select grp.First()).ToList();

Alternatively IEnumerable.Distinct() method can take an IEqualityComparer in the following style
var distinctList = myList.Distinct(
    EqualityFactory.Create<FooType>(
    (x, y) => x.Prop1 == y.Prop1 && x.Prop2 == y.Prop2)
    ).ToList();

Here is the source code of the factory:
// -------------------------------------
// 
//  Copyleft (c) All rights released.
// 
// -------------------------------------

namespace Common.Helpers
{
  using System;
  using System.Collections.Generic;

  /// <summary>
  /// Factory to produce instances of the <see cref="EqualityComparer{T}" /> class.
  /// the type of an object.
  /// </summary>
  public class EqualityFactory
  {
    /// <summary>
    /// Creates a new instance of the <see cref="IEqualtyComparer{T}" /> class.
    /// </summary>
    /// the type of an object.
    /// returns an instance of equality comparer.
    public static IEqualityComparer<T> Create<T>(Func<T, T, bool> funcComparer)
    {
      return new ImpEqualityComparer<T>(funcComparer);
    }

    #region internal equality comparer class

    /// <summary>
    /// Implements <see cref="IEqualityComparer" /> interface.
    /// </summary>
    /// the type of an object.
    private class ImpEqualityComparer<T> : IEqualityComparer<T>
    {
      /// <summary>the comparison function.</summary>
      private Func<T, T, bool> comparer;

      /// <summary>the default comparison function.</summary>
      private IEqualityComparer<T> defaultComparer;

      /// <summary>
      /// Initializes a new instance of the <see cref="ImpEqualityComparer{T}" /> class.
      /// </summary>
      public ImpEqualityComparer(Func<T, T, bool> delegateComparer)
      {
        this.comparer = delegateComparer;
        this.defaultComparer = EqualityComparer<T>.Default;
      }

      /// <summary>
      /// Compares objects by using equality comparer.
      /// </summary> 
      /// returns True if objects are equal; otherwise False. 
      bool IEqualityComparer<T>.Equals(T x, T y)
      {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;

        return this.comparer(x, y);
      }

      /// <summary>
      /// Get hash code by the equality comparer.
      /// </summary>
      /// returns the hash code.
      int IEqualityComparer<T>.GetHashCode(T obj)
      {
        // In order to use the comparer, the hash code has to be the same.
        return 0;
      }
    }

    #endregion
  }
}
// class EqualityFactory

If hash code comparison is required, use an LambdaEqualityComparer class instead. In the following example, the distinct result will be based on objects' Name, and other properties (Prop1 and Prop2).
var distinctList = myList.Distinct(
    new LambdaEqualityComparer<FooType>(
    (x, y) => x.Prop1 == y.Prop1 && x.Prop2 == y.Prop2,
    (t) => t.Name.GetHashCode()) // assume t.Name is a string
    ).ToList();

See LambdaEqualityComparer class source code:
// -------------------------------------
// LambdaEqualityComparer.cs
// -------------------------------------

namespace Common.Helpers
{
  using System;
  using System.Collections.Generic;

  /// <summary>
  /// Implements <see cref="IEqualityComparer" /> interface.
  /// </summary>
  /// the type of an object.
  public class LambdaEqualityComparer<T> : IEqualityComparer<T>
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="LambdaEqualityComparer{T}" /> class.
    /// </summary>
    public LambdaEqualityComparer(
      Func<T, T, bool> funcEquals, Func<T, int> funcGetHashCode = null)
    {
      if (funcEquals == null)
      {
        throw new ArgumentNullException("funcEquals", "An equals function is required.");
      }

      this.GetHashCodeMethod = funcGetHashCode;
      this.EqualsMethod = funcEquals;
    }

    /// <summary>Gets and sets the method used to compute equals.</summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }

    /// <summary>Gets and sets the method used to compute a hash code.</summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    /// <summary>
    /// Implements Equals from <see cref="IEqualityComparer{T}" /> interface.
    /// </summary>
    /// returns result of the comparison.
    bool IEqualityComparer<T>.Equals(T x, T y)
    {
      return this.EqualsMethod(x, y);
    }

    /// <summary>
    /// Implements GetHashCode from <see cref="IEqualityComparer{T}" /> interface.
    /// </summary>
    /// returns hash code.
    int IEqualityComparer<T>.GetHashCode(T obj)
    {
      if (this.GetHashCodeMethod == null) return 0;

      return this.GetHashCodeMethod(obj);
    }
  }  
}// class LambdaEqualityComparer

More advanced, to wrap GetHashCode into a projection (MiscUtil):
// -------------------------------------
// ProjectionEqualityComparer.cs
// -------------------------------------

namespace Common.Helpers
{
  using System;
  using System.Collections.Generic;

  /// <summary>
  /// Comparer uses projected keys from source element.
  /// </summary>
  /// Type of elements the comparer to project.
  /// Type of the key projected from the element.
  public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource>
  {
    /// <summary>the comparison function.</summary>
    private readonly Func<TSource, TKey> projection;

    /// <summary>the equality comparer.</summary>
    private readonly IEqualityComparer<TKey> comparer;

    /// <summary>
    /// Initializes a new instance of the <see cref="ProjectionEqualityComparer{TSource, TKey}" />.
    /// Using default comparer for the projected type.
    /// </summary>
    public ProjectionEqualityComparer(
      Func<TSource, TKey> projection) : this(projection, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ProjectionEqualityComparer{TSource, TKey}" />.
    /// The default comparer for the projected type is used if a comparer not specified.
    /// </summary>
    public ProjectionEqualityComparer(
      Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer)
    {
      if (projection == null)
      {
        throw new ArgumentNullException("projection");
      }

      this.comparer = comparer ?? EqualityComparer<TKey>.Default;
      this.projection = projection;
    }

    /// <summary>
    /// Compares the two specified values for equality by applying the projection to
    /// each value and then using the equality comparer on the resulting keys.
    /// Null references are never passed to the projection.
    /// </summary>
    /// returns True if objects are equal; otherwise False.
    public bool Equals(TSource x, TSource y)
    {
      if (x == null && y == null) return true;
      if (x == null || y == null) return false;

      return this.comparer.Equals(this.projection(x), this.projection(y));
    }

    /// <summary>
    /// Produces a hash code for the given value by projecting it and then
    /// asking the equality comparer to find the hash code of the resulting key.
    /// </summary>
    /// returns the hash code.
    public int GetHashCode(TSource obj)
    {
      if (obj == null)
      {
        throw new ArgumentNullException("obj");
      }
      return this.comparer.GetHashCode(this.projection(obj));
    }
  }
}// class ProjectionEqualityComparer

Or using string value of a property name (see Cuemon.Reflection):
// -------------------------------------
// 
//  Copyleft (c) All rights released.
// 
// -------------------------------------

namespace Common.Helpers
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Reflection;

  /// <summary>
  /// Implements <see cref="IEqualityComparer{T}" /> interface.
  /// </summary>
  /// the type of an object.
  public class PropertyEqualityComparer<T> : IEqualityComparer<T>
  {
    /// <summary>the <see cref="PropertyInfo"/> object.</summary>
    private PropertyInfo propertyInfo;

    /// <summary>
    /// Initializes a new instance of the <see cref="PropertyEqualityComparer{T}" /> class.
    /// </summary>
    public PropertyEqualityComparer(string propertyName)
    {
      // store a reference to the <see cref="PropertyInfo"/> for use in comparison
      this.propertyInfo = typeof(T).GetProperty(
        propertyName, 
        BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public
        );
      if (this.propertyInfo == null)
      {
        var message = string.Format(
          "The name '{0}' is not a property of type {1}.", 
          propertyName, typeof(T));
        throw new ArgumentException(message);
      }
    }

    #region Methods :: Implments IEqualityComparer

    /// <summary>
    /// Implements Equals from <see cref="IEqualityComparer{T}" /> interface.
    /// </summary>
    /// returns result of the comparison.
    public bool Equals(T x, T y)
    {
      // get the current value of the comparison property of x and of y
      var valueX = this.propertyInfo.GetValue(x, null);
      var valueY = this.propertyInfo.GetValue(y, null);
    
      // consider equal only if both xValue and yValue are null
      if (valueX == null)
      {
        return valueY == null;
      }

      // use default comparer
      return valueX.Equals(valueY);
    }

    /// <summary>
    /// Implements GetHashCode from <see cref="IEqualityComparer{T}" /> interface.
    /// </summary>
    /// returns hash code.
    public int GetHashCode(T obj)
    {
      var propertyValue = this.propertyInfo.GetValue(obj, null);

      if (propertyValue != null)
      {
        return propertyValue.GetHashCode();
      }
      return 0;
    }
  
    #endregion
  }  
}

Happy coding!