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:
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.
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<
>
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:
public static void InitializeService(DataServiceConfiguration config)
{
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
by:
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:
[DataServiceKey("Name")]
public class ProcessModel
{
public string Name { get; set; }
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:
public class DataModel
{
public DataModel()
{
var processProjection = from p in Process.GetProcesses()
select new ProcessModel
{
Name = p.ProcessName,
ID = p.Id
};
Processes = processProjection.AsQueryable();
}
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:
[DataServiceKey("ID")]
public class EmployeeModel
{
public int ID { get; set; }
public int DepartmentID { get; set; }
public int AddressID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int StreetNumber { get; set; }
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:
[DataServiceKey("ID")]
public class DepartmentModel
{
public int ID { get; set; }
public string Name { get; set; }
}
We create a data model exposing both models as follows:
public class DataModel
{
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
};
}
}
public IQueryable<EmployeeModel> Employees { get; private set; }
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>
{
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:
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:
public class DataModelProxy : DataServiceContext
{
public DataModelProxy(Uri serviceRoot)
: base(serviceRoot)
{
}
public IQueryable<EmployeeModel> Employees
{
get { return CreateQuery<EmployeeModel>("Employees"); }
}
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.