Working with Local Entities in Astoria Client

Although the Astoria client is really designed to query data existing in some remote service location, it does keep a local copy of all entities it has seen. If the MergeOption is not set to NoTracking, you can find all of the entities currently been tracked in the DataServiceContext.Entities property. The property is defined as:

ReadOnlyCollection<EntityDescriptor> Entities

where EntityDescriptor is a class that gives you access to the entity instance, the ETag and the state of the entity (changed, deleted, etc). The easiest way to query local copies of entities is to use LINQ to Objects. For example, if you are looking for a Customer who’s CustomerID is 0, and you want to see if it exists locally first, then you can write:

var q = from ed in Context.Entities
where ed.Entity is Customer
&& ((Customer)ed.Entity).ID == 0
select (Customer)ed.Entity;

Customer cust = q.FirstOrDefault();

In the above example, when cust is not null, you can work with the copy and still have the context track the changes you make to the entity. If cust is null, you can then query the remote service and the next time you run this again, a local copy will exist.

The key to this is, of course, is to have a singleton DataServiceContext for all of your data access needs. In a sense, you are keeping a local "data store” and when possible, use it’s data instead of the remote one. Ultimately, you would want to have it retrieve remote data on demand.

A simple data store may look like this:

public class DataStore
{
static DataServiceContext ctx = new DataServiceContext(new Uri(“ServiceUri”, UriKind.Absolute));

    public static DataServiceContext Context
{
get { return ctx; }
}

    public static IQueryable<T> QueryLocal<T>()
{
return (from ed in ctx.Entities.AsQueryable()
where ed.Entity is T
select (T)ed.Entity);
}

    public static IQueryable<T> QueryLocal<T>(Expression<Func<T,bool>> predicate)
{
return (from ed in ctx.Entities.AsQueryable()
where ed.Entity is T
select (T)ed.Entity).Where(predicate);
}
}

With this setup, you can query the remote store with DataStore.Context.CreateQuery<T>(), and you can query the local store with DataStore.QueryLocal<T>(). The above Customer example simply become:

Customer cust = DataStore.QueryLocal<Customer>(c=>c.ID==0).FirstOrDefault();

Taking it one step further, you can imagine having a unified “QueryStore<T>()” function that queries local data first, and if it didn’t find anything, it would go ahead and ask the service for data. This approach can significantly reduce the amount of data you need to move across the wire for your service.

Of course, just like everything else in life, this approach also has trade-offs. The biggest one comes from the fact that the local copies are not “partitioned” or “indexed” in anyway. This means even if all you are looking for is one customer, you may have to loop through thousands of other irrelevant entities. In situation like this, you must make a decision to either minimize the bandwidth or maximize the performance – it could be faster if you just query the remote service instead. In short, whether having a local data store suites your needs depends on the structure of your data. Applied currently, it can significantly optimize your data service experience.