In previous blog post I briefly touched OData protocol by showing quick usage of OData feed and then implemented ever simple WCF data service with using Entity Framework.

Now, let’s imagine that we have some data in memory and would like to expose it. Is it hard or easy?

Querying in memory data

If you can represent your data as IQueryable then, for most cases, you are fine. There is an extension method to IEnumerable called AsQueryable, so as long as your data can be accessed as IEnumerable you can make it IQueryable.

Sample data kept in memory

Now, let’s create some sample data:

private IList<Sport> _sports = new List<Sport>();
private IList<League> _leagues = new List<League>();

private void Populate()
{
    _sports = new List<Sport>();
    _leagues = new List<League>();

    _sports.Add(new Sport() { Id = 1, Name = "Swimming"});
    _sports.Add(new Sport() { Id = 2, Name = "Skiing"});

    var sport = new Sport() { Id = 3, Name = "Football"};
    var league = new League() { Id = 1, Name = "EURO2012", Region = "Poland&Ukraine" };
    sport.AddLeague(league);
    var league1 = new League() { Id = 2, Name = "UK Premier League", Region = "UK" };
    sport.AddLeague(league1);
    _sports.Add(sport);

    var league2 = new League() { Id = 3, Name = "Austria Premier League", Region = "Austria" };
    _leagues.Add(league);
    _leagues.Add(league1);
    _leagues.Add(league2);

    _sports.Add(new Sport() { Id = 4, Name = "Tennis"});
    _sports.Add(new Sport() { Id = 5, Name = "Volleyball"});
}

Absolutely nothing smart or difficult there, but at least to give you an idea about dataset we have.

Few things to make it happen

To expose sports and leagues we would need to add public properties to our data service implementation like below:

public IQueryable<Sport> Sports { get { return _sports.AsQueryable(); } }
public IQueryable<League> Leagues { get { return _leagues.AsQueryable(); } }

Another important thing about exposing data is that you have to indicate key for your entities with DataServiceKey attribute.

To make our service bit more realistic I’m going to add caching as well.

Complete source code of service
public class SportsWcfDataService : DataService<SportsData>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        // To know if there are issues with your data model
        config.UseVerboseErrors = true;
    }

    protected override void HandleException(HandleExceptionArgs args)
    {
        // Put breakpoint here to see possible problems while accessing data
        base.HandleException(args);
    }

    protected override SportsData CreateDataSource()
    {
        return SportsData.Instance;
    }

    protected override void OnStartProcessingRequest(ProcessRequestArgs args)
    {
        base.OnStartProcessingRequest(args);
        var cache = HttpContext.Current.Response.Cache;
        cache.SetCacheability(HttpCacheability.ServerAndPrivate);
        cache.SetExpires(HttpContext.Current.Timestamp.AddSeconds(120));
        cache.VaryByHeaders["Accept"] = true;
        cache.VaryByHeaders["Accept-Charset"] = true;
        cache.VaryByHeaders["Accept-Encoding"] = true;
        cache.VaryByParams["*"] = true;
    }
}

public class SportsData
{
    static readonly SportsData _instance = new SportsData();

    public static SportsData Instance
    {
        get { return _instance; }
    }

    private SportsData()
    {
        Populate();
    }

    private void Populate()
    {
        // Population of data
        // Above in this post 
    }

    private IList<Sport> _sports = new List<Sport>();
    public IQueryable<Sport> Sports { get { return _sports.AsQueryable(); } }

    private IList<League> _leagues = new List<League>();
    public IQueryable<League> Leagues { get { return _leagues.AsQueryable(); } }
}

[DataServiceKey("Id")]
public class Sport
{
    public int Id { get; set; }
    public string Name { get; set; }
    public void AddLeague(League league)
    {
        _leagues.Add(league);
    }

    private IList<League> _leagues = new List<League>();
    public IEnumerable<League> Leagues { get { return _leagues; } }
}

[DataServiceKey("Id")]
public class League
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Region { get; set; }
}
Some results of our work

With this URL http://localhost:49936/SportsService.svc/ service can be seen:

<service xml:base="http://localhost:49936/SportsService.svc/">
 <workspace>
  <atom:title>Default</atom:title>
  <collection href="Sports"><atom:title>Sports</atom:title></collection>
  <collection href="Leagues"><atom:title>Leagues</atom:title></collection>
 </workspace>
</service>

Now, you can access data via URL or by writing C# Linq queries if connected with client app or LinqPad. Following request:

http://localhost:49936/SportsService.svc/Leagues()?$filter=Name eq ‘EURO2012’&$select=Region

Would produce result containing this:

<d:Region>Poland&amp;Ukraine</d:Region>

What if you need to edit data?

If you need your data to be updatable, your SportsData would need to implement System.Data.Services.IUpdatable interface.

What if your datasource is not just few collections in memory?

What if you have very special data source, or you cannot simply keep your data in memory like collections of some data? This could be tricky or very tricky, depending on your data source, of course. But anyway you would need to implement interface IQueryable by hand if not provided by your data source.

Here is step-by-step walkthrough on msdn. Only by size of article you could imagine it is not trivial task to do. But if you managed to do it you can be proud, because you can implement your own Linq provider. (Do you remember NHibernate introducing support of Linq? It was big feature for product, roughly all they did was implementation of IQueryably by their NhQueryable.)

Why do I learn OData?

First of all I have to investigate if my team can use it. (Depending on outcome you might see more posts on this topic from me.) And another reason is that it is very exciting topic, about which I knew so little.

Very disappointing to admit that I start to really understand things when I touch them for reason and that reading dozen of blog posts on topic is useless unless I do something meaningful related to matters in those posts.

So, my Dear Reader, if you are reading this post and have read my previous post, but never tried OData I would be really pleased if you invest 10-30 or more minutes of your time to play with OData.