Click here to Skip to main content
Click here to Skip to main content

Advanced using OData in .NET: WCF Data Services

By , 16 Dec 2010
Rate this:
Please Sign up or sign in to vote.

Introduction

In this article, I will give code samples showcasing the usage of OData in .NET (WCF Data Services). The samples will go in increasing order of complexity, addressing more and more advanced scenarios.

Background

For some background on what is OData, I wrote an introductory article on OData a few months ago: An overview of Open Data Protocol (OData). In that article, I showed what the Open Data Protocol is and how to consume it with your browser.

In this article, I'll show how to expose data as OData using the .NET Framework 4.0. The technology in .NET Framework 4.0 used to expose OData is called WCF Data Services. Prior to .NET 4.0, it was called ADO.NET Data Services, and prior to that, Astoria, which was Microsoft's internal project name.

Using the code

The code sample contains a Visual Studio 2010 Solution. This Solution contains a project for each sample I give here.

The Solution also contains a solution folder named "DB Scripts" with three files: SchemaCreation.sql (creates a schema with three tables into a pre-existing database), DataInsert.sql (inserts sample data in the tables), and SchemaDrop.sql (drops the tables and schema if needed).

We use the same database schema for the entire project:

Schema.JPG

Hello World: Your database on the Web

For the first sample, we'll do a Hello World: we'll expose our schema on the web. To do this, we need to:

  • Create an Entity Framework model of the schema
  • Create a WCF Data Service
  • Map the Data Context to our entity model
  • Declare permissions

Creating an Entity Framework model isn't the goal of this article. You can consult MSDN pages to obtain details: http://msdn.microsoft.com/en-us/data/ef.aspx.

EntityModel.JPG

Creating a WCF Data Service is also quite trivial; create a new item in the project:

We now have to map the data context to our entity model. This is done by editing the code-behind of the service and simply replacing:

public class EmployeeDataService : DataService<
/* TODO: put your data source class name here */ >

by:

public class EmployeeDataService : DataService<ODataDemoEntities>

Finally, we now have to declare permissions. By default, Data Services are locked down. We can open read and write permission. In this article, we'll concentrate on the read permissions. We simply replace:

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
  // TODO: set rules to indicate which entity sets
  // and service operations are visible, updatable, etc.
  // Examples:
  // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
  // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

by:

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
  config.SetEntitySetAccessRule("Employees", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Addresses", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Departments", EntitySetRights.AllRead);

  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

There you go; you can run your project and have the three tables exposed as OData feeds on the web.

Exposing another Data Model

Unfortunately, the previous example is what 95% of the samples you'll find on the web are about. But WCF Data Services can be so much more.

In this section, we'll build a data model from scratch, one that isn't even connected to a database. What could we connect on... of course! The list of processes running on your box!

In order to do this, we're going to have to:

  • Create a process model class
  • Create a data model exposing the processes
  • Create a WCF Data Service
  • Map the Data Context to our data model
  • Declare permissions

We create the process model class as follows:

 /// <summary>Represents the information of a process.</summary>
[DataServiceKey("Name")]
public class ProcessModel
{
  /// <summary>Name of the process.</summary>
  public string Name { get; set; }

  /// <summary>Process ID.</summary>
  public int ID { get; set; }
}

Now the key take aways here are:

  • Always use simple types (e.g., integers, strings, date, etc.): they are the only ones to map to Entity Data Model (EDM) types.
  • Always declare the key properties (equivalent to a Primary Key column in a database table) with the DataServiceKey attribute. The concept of key is central in OData, since you can single out an entity by using its ID.

If you violate one of those two rules, you'll have errors in your Web Service and not much way to know why.

We then create the data model, exposing the processes like this:

/// <summary>Represents a data model exposing processes.</summary>
public class DataModel
{
  /// <summary>Default constructor.</summary>
  /// <remarks>Populates the model.</remarks>
  public DataModel()
  {
    var processProjection = from p in Process.GetProcesses()
                            select new ProcessModel
                            {
                              Name = p.ProcessName,
                              ID = p.Id
                            };

    Processes = processProjection.AsQueryable();
  }

  /// <summary>Returns the list of running processes.</summary>
  public IQueryable<ProcessModel> Processes { get; private set; }
}

Here, we could have exposed more than one process for completeness, but we opted for simplicity.

The key here is to:

  • Have only properties returning IQueryable of models.
  • Populate those collections in the constructor

Here we populate the model list directly, but sometimes (as we'll see in the next section), you can simply populate it with a deferred query, which is more performing.

Creation and mapping of the data service and the permission declaration works the same way as the previous sample. After you've done that, you have an OData endpoint exposing the processes on your computer. You can interrogate this endpoint with any type of client, such as LinqPad.

This example isn't very useful in itself, but it shows that you can expose any type of data as an OData endpoint. This is quite powerful because OData is a rising standard, and as you've seen, it's quite easy to expose your data that way.

You could, for instance, have your production servers expose some live data (e.g., a number of active sessions) as OData that you could consume at any time.

Exposing a transformation of your database

Another very useful scenario, somehow a combination of the previous ones, is to expose data from your database with a transformation. Now that might be accomplished by performing mappings in the entity model, but sometimes you might not want to expose the entity model directly, or you might not be able to do the mapping in the entity model. For instance, the OData data objects might be out of your control but you must use them to expose the data.

In this sample, we'll flatten the employee and its address into one entity at the OData level.

  • Create an Entity Framework model of the schema
  • Create an employee model class
  • Create a department model class
  • Create a data model exposing both model classes
  • Create a WCF Data Service
  • Map the Data Context to our data model
  • Declare permissions

Creation of the Entity Framework model is done the same way as in the Hello World section.

We create the employee model class as follows:

 /// <summary>Represents an employee.</summary>
[DataServiceKey("ID")]
public class EmployeeModel
{
  /// <summary>ID of the employee.</summary>
  public int ID { get; set; }

  /// <summary>ID of the associated department.</summary>
  public int DepartmentID { get; set; }

  /// <summary>ID of the address.</summary>
  public int AddressID { get; set; }

  /// <summary>First name of the employee.</summary>
  public string FirstName { get; set; }

  /// <summary>Last name of the employee.</summary>
  public string LastName { get; set; }

  /// <summary>Address street number.</summary>
  public int StreetNumber { get; set; }

  /// <summary>Address street name.</summary>
  public string StreetName { get; set; }
}

We included properties from both the employee and the address, hence flattening the two models. We also renamed the EmployeeID to ID.

We create the department model class as follows:

 /// <summary>Represents a department.</summary>
[DataServiceKey("ID")]
public class DepartmentModel
{
  /// <summary>ID of the department.</summary>
  public int ID { get; set; }

  /// <summary>Name of the department.</summary>
  public string Name { get; set; }
}

We create a data model exposing both models as follows:

/// <summary>Represents a data model exposing
/// both the employee and the department.</summary>
public class DataModel
{
  /// <summary>Default constructor.</summary>
  /// <remarks>Populates the model.</remarks>
  public DataModel()
  {
    using (var dbContext = new ODataDemoEntities())
    {
      Departments = from d in dbContext.Department
                    select new DepartmentModel
                    {
                      ID = d.DepartmentID,
                      Name = d.DepartmentName
                    };

      Employees = from e in dbContext.Employee
                  select new EmployeeModel
                  {
                    ID = e.EmployeeID,
                    DepartmentID = e.DepartmentID,
                    AddressID = e.AddressID,
                    FirstName = e.FirstName,
                    LastName = e.LastName,
                    StreetNumber = e.Address.StreetNumber,
                    StreetName = e.Address.StreetName
                  };
    }
  }

  /// <summary>Returns the list of employees.</summary>
  public IQueryable<EmployeeModel> Employees { get; private set; }

  /// <summary>Returns the list of departments.</summary>
  public IQueryable<DepartmentModel> Departments { get; private set; }
}

We basically do the mapping when we populate the employee query. Here, as opposed to the previous example, we don't physically populate the employees, we define a query to fetch them. Since LINQ always defines a deferred query, the query simply maps information.

We then create a WCF Data Service, map the data context to the data model, and declare permissions as follows:

public class EmployeeDataService : DataService<DataModel>
{
  // This method is called only once to initialize service-wide policies.
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("Employees", EntitySetRights.AllRead);
    config.SetEntitySetAccessRule("Departments", EntitySetRights.AllRead);

    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
  }
}

This scenario is quite powerful. Basically, you can mix data from the database and from other sources, transform it, and expose it as OData, while still beneficiating of the query power of your model (e.g., the database).

Service Operations

Another useful scenario covered by WCF Data Services, the .NET 4.0 implementation of OData, is the ability to expose a service, with input parameters, but where the output is treated as other entity sets in OData, that is, queryable in every way.

This is very powerful since the expressiveness of queries in OData is much less than in LINQ, i.e., there are a lot of queries in LINQ you just can't do in OData. This is quite understandable since queries are packed in a URL. Service Operations fill that gap by allowing you to take parameters in, perform a complicated LINQ query, and return the result as a queryable entity-set.

Why would you query an operation being the result of a query? Well, for one thing, you might want to page on the result, using take & skip. But it might be that the result still represents a mass of data and you're interested in only a fraction of it. For instance, you could have a Service Operation returning the individuals in a company with less than a given amount of sick leave; for a big company, that is still a lot of data!

In this sample, we'll expose a Service Operation taking a number of employees in input and returning the departments with at least that amount of employees.

  • Create an Entity Framework model of the database schema
  • Create a WCF Data Service
  • Map the Data Context to our entity model
  • Add a Service Operation
  • Declare permissions

The first three steps are identical to the Hello World sample.

We define a Service Operation within the data service as follows:

[WebGet]
public IQueryable<Department> GetDepartmentByMembership(int employeeCount)
{
  var departments = from d in this.CurrentDataSource.Department
                    where d.Employee.Count >= employeeCount
                    select d;

  return departments;
}

We then add the security as follows:

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
  config.SetEntitySetAccessRule("Employee", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Address", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Department", EntitySetRights.AllRead);

  config.SetServiceOperationAccessRule("GetDepartmentByMembership", 
                                       ServiceOperationRights.AllRead);

  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

Notice that we needed to enable the read on the Service Operation as well.

We can then hit the Service Operation with a URL such as: http://localhost:37754/EmployeeDataService.svc/GetDepartmentByMembership?employeeCount=2.

You can read more about Service Operations in this MSDN article.

Consuming OData using .NET Framework

In order to consume OData, you can simply hit the URLs with an HTTP-GET using whatever web library at your disposal. In .NET, you can do better than that.

You can simply add a reference to your OData service and Visual Studio will generate proxies for you. This is quick and dirty, and works very well.

In cases where you've defined your own data model (as in our database transformation sample), you might want to share those models as data contracts between the server and the client. You would then have to define the proxy yourself, which isn't really hard:

/// <summary>Proxy to our data model exposed as OData.</summary>
public class DataModelProxy : DataServiceContext
{
  /// <summary>Constructor taking the service root in parameter.</summary>
  /// <param name="serviceRoot"></param>
  public DataModelProxy(Uri serviceRoot)
  : base(serviceRoot)
  {
  }

  /// <summary>Returns the list of employees.</summary>
  public IQueryable<EmployeeModel> Employees
  {
    get { return CreateQuery<EmployeeModel>("Employees"); }
  }

  /// <summary>Returns the list of departments.</summary>
  public IQueryable<DepartmentModel> Departments
  {
    get { return CreateQuery<DepartmentModel>("Departments"); }
  }
}

Basically, we derive from System.Data.Services.Client.DataServiceContext and define a property for each entity set and create a query for each. We can then use it this way:

static void Main(string[] args)
{
  var proxy = new DataModelProxy(new Uri(
      @"http://localhost:9793/EmployeeDataService.svc/"));
  var employees = from e in proxy.Employees
                  orderby e.StreetName
                  select e;

  foreach (var e in employees)
  {
    Console.WriteLine("{0}, {1}", e.LastName, e.FirstName);
  }
}

The proxy basically acts as a data context! We treat it as any entity set source and can do queries on it. This is quite powerful since we do not have to translate the queries into URLs ourselves: the platform takes care of it!

Conclusion

We have seen different scenarios using WCF Data Services to expose and consume data. We saw that there is no need to limit ourselves to a database or an entity framework model. We can also expose Service Operations to do queries that would be otherwise impossible to do through OData, and we've seen an easy way to consume OData on a .NET client.

I hope this opens up the possibilities around OData. We typically see samples where a database is exposed on the web and it looks like Access 1995 all over again. But OData is much more than that: it enables you to expose your data on the web, but to present it the way you want and to control its access. It's blazing fast to expose data with OData, and you do not need to know the query needs of the client since the protocol takes care of it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Vincent-Philippe Lauzon
Architect CGI
Canada Canada
Vincent-Philippe is a Senior Solution Architect working in Montreal (Quebec, Canada).
 
His main interests are Windows Azure, .NET Enterprise suite (e.g. SharePoint 2013, Biztalk Server 2010) & the new .NET 4.5 platforms.

Comments and Discussions

 
QuestionData Transformation PinmemberHassoon328-Feb-14 20:10 
QuestionAdd or Modify Data in Tables Pinmemberant111117-Oct-13 4:24 
AnswerRe: Add or Modify Data in Tables Pinmemberandrew.chudley23-Oct-13 10:35 
QuestionContext Lifetime Pinmemberdgg2313-Mar-13 15:09 
QuestionCan we export the data received to .csv, .xls, .dat, .pdf formats? Pinmembergogsthecoder19-Feb-13 0:41 
AnswerRe: Can we export the data received to .csv, .xls, .dat, .pdf formats? PinmemberVincent-Philippe Lauzon19-Feb-13 4:35 
GeneralExcelent PinmemberAlejandro Robleto4-Dec-12 19:23 
GeneralMy vote of 5 PinmemberRahul Rajat Singh18-Nov-12 22:37 
QuestionQuerying service operations in the .NET client library PinmemberStevePilon24-Sep-12 3:39 
GeneralMy vote of 5 PinmemberSpringsteenFan28-Aug-12 0:54 
GeneralMy vote of 5 Pinmemberr2jf12-Jul-12 10:38 
GeneralMy vote of 5 Pinmembersri_mncl3-May-12 4:53 
GeneralMy vote of 5 PinmemberDanielLarsenNZ25-Mar-12 10:27 
QuestionHow to limit the properties returned by the Data Service PinmemberMember 364734522-Mar-12 9:13 
SuggestionOther easy to use ORM packages PinmemberPKochar11-Oct-11 11:38 
QuestionRelationship Pinmemberbakkorh14-Aug-11 20:54 
GeneralMy vote of 5 PinmemberScorcherChar29-Jun-11 0:34 
QuestionSource Code Download? PinmemberVinceJS10-Apr-11 10:54 
AnswerRe: Source Code Download? PinmemberVincent-Philippe Lauzon11-Apr-11 6:06 
AnswerRe: Source Code Download? PinmemberVincent-Philippe Lauzon11-Apr-11 7:20 
Generalgood PinmemberPranay Rana16-Dec-10 21:47 
GeneralMy vote of 4 PinmemberDave Elliott13-Dec-10 3:23 
QuestionWhat about changes ? PinmemberNicolas Penin11-Dec-10 4:17 
AnswerRe: What about changes ? PinmemberVincent-Philippe Lauzon13-Dec-10 7:56 
GeneralRe: What about changes ? PinmemberTim McCurdy29-Apr-11 6:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140421.2 | Last Updated 16 Dec 2010
Article Copyright 2010 by Vincent-Philippe Lauzon
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid