Ever had a special entity where there can be only one of its kind, but bothered by having to define a one-element entity set for it? Ever wanted to request a special entity but bothered by needing to find out its key first? Use a singleton! Singleton is newly introduced in OData V4, to allow developers name such special entity, and it can be addressed directly by its name from the service root.

Let's think about a universal scenario which can benefit from singleton:

We want to have a model to present the staff information of a company.

So first, we define an EntityType named EmployeeType to represent the schema of an employee information, and accordingly define EmployeeSet (which is an EntitySet) to actually have the information of each employee.

And second, we also want to have an entity to present the information of the company, like company name, logo, strategy etc. First step is to define an EntityType named CompanyType of cause, what about then, define an EntitySet named CompanySet? EntitySet means a collection of entities, but we only need one entity for company! Of cause you can have just one entity in the EntitySet, like what we did before V4. But to have one entity, you are forced to have a collection of entities! Singleton in V4 can release you from such an awkward situation.

Defining company as a singleton at least have 2 advantages than defining company as an EntitySet:

  • It is more straightforward in concept. Singleton is designed to present a special entity, just like company.
  • You can directly access company from service root by querying ".svc/Company", without appending the CompanyID.

How to define and consume Singleton

Just little effort is needed for a service to support singleton. Basically, Singleton is quite similar with EntitySet, and the biggest difference of singleton and entityset is that when writing a singleton, we call CreateODataEntryWriter, rather than CreateODataFeedWriter for writing EntitySet. Same to call CreateODataEntryReader for reading a singleton.

Following shows the simple steps to support singleton (Sample Code).

Service side

1. Define singleton in EDM model

2. Parse singleton segment in querying URI

When receiving a query uri, like ".svc/Company", service calls ODataUriParser to parse the uri, and in this case the last segment of the uri should be SingletonSegment.

3. Write a singleton

To write the response, service calls CreateODataEntryWriter to write the company info in json payload. To simply the example, we directly write the payload into a stream.

Client side

After sending the query ".svc/Company", client can receive a response from wire if everything works well. So client just need to call CreateODataEntryReader to read the singleton out.

We have response in the stream in this example.

Singleton advanced features

The example above shows the simplest way to use a singleton, but there are some other advanced features of singleton:

1. Query singleton with query option

Singleton is an entity after all, so only $select and $expand are supported when singleton is the resource path.

Sample Uri:

.svc/Company?$select=Name

2. Navigation

  • Singleton can have EntitySet, contained EntitySet or singleton as navigation property.
  • Entityset can have singleton as navigation property

Below is a code snippet to define navigation of singleton.

Sample Uri:

.svc/Company/Employees

.svc/Company?$expand=Employees

.svc/Employees(1)/Company

3. Bound Action/Function

Since bound action and function is defined on EntityType, singleton can inherited action and function from its defining EntityType.

Sample Uri:

GET .svc/Company/NameSpace.GetEmployeesCount() // GetEmployeesCount is a function

POST .svc/Company/NameSpace.IncreaseRevenue with payload:  {"IncreaseValue":20000}  // IncreaseRevenue is an action