Walkthrough: Accessing an OData Service by Using Type Providers


This guide was written for F# 3.0 and will be updated. See FSharp.Data for up-to-date, cross-platform type providers.


The API reference links will take you to MSDN. The docs.microsoft.com API reference is not complete.

OData, meaning Open Data Protocol, is a protocol for transferring data over the Internet. Many data providers expose access to their data by publishing an OData web service. You can access data from any OData source in F# 3.0 using data types that are automatically generated by the ODataService type provider. For more information about OData, see https://msdn.microsoft.com/library/da3380cc-f6da-4c6c-bdb2-bb86afa59d62.

This walkthrough shows you how to use the F# ODataService type provider to generate client types for an OData service and query data feeds that the service provides.

This walkthrough illustrates the following tasks, which you should perform in this order for the walkthrough to succeed:

  • Configuring a client project for an OData service

  • Accessing OData types

  • Querying an OData service

  • Verifying the OData requests

Configuring a client project for an OData service

In this step, you set up a project to use an OData type provider.

To configure a client project for an OData service

  • Open an F# Console Application project, and then add a reference to the System.Data.Services.Client Framework assembly.

  • Under Extensions, add a reference to the FSharp.Data.TypeProviders assembly.

Accessing OData types

In this step, you create a type provider that provides access to the types and data for an OData service.

To access OData types

  • In the Code Editor, open an F# source file, and enter the following code.
open Microsoft.FSharp.Data.TypeProviders

type Northwind = ODataService<"http://services.odata.org/Northwind/Northwind.svc/">

let db = Northwind.GetDataContext()
let fullContext = Northwind.ServiceTypes.NorthwindEntities()

In this example, you have invoked the F# type provider and instructed it to create a set of types that are based on the OData URI that you specified. Two objects are available that contain information about the data; one is a simplified data context, db in the example. This object contains only the data types that are associated with the database, which include types for tables or feeds. The other object, fullContext in this example, is an instance of System.Data.Linq.DataContext and contains many additional properties, methods, and events.

Querying an OData service

In this step, you use F# query expressions to query the OData service.

To query an OData service

  1. Now that you've set up the type provider, you can query an OData service.
    OData supports only a subset of the available query operations. The following operations and their corresponding keywords are supported:
  • projection (select)

  • filtering (where, by using string and date operations)

  • paging (skip, take)

  • ordering (orderBy, thenBy)

  • AddQueryOption and Expand, which are OData-specific operations

For more information, see LINQ Considerations (WCF Data Services).
If you want all of the entries in a feed or table, use the simplest form of the query expression, as in the following code:

query {
  for customer in db.Customers do
  select customer
} |> Seq.iter (fun customer ->
                  printfn "ID: %s\nCompany: %s" customer.CustomerID customer.CompanyName
                  printfn "Contact: %s\nAddress: %s" customer.ContactName customer.Address
                  printfn "         %s, %s %s" customer.City customer.Region customer.PostalCode
                  printfn "%s\n" customer.Phone)
  1. Specify the fields or columns that you want by using a tuple after the select keyword.
  query { 
    for cat in db.Categories do
    select (cat.CategoryID, cat.CategoryName, cat.Description)
  } |> Seq.iter (fun (id, name, description) ->
                    printfn "ID: %d\nCategory: %s\nDescription: %s\n" id name description)
  1. Specify conditions by using a where clause.
query {
  for employee in db.Employees do
  where (employee.EmployeeID = 9)
  select employee
} |> Seq.iter (fun employee ->
                  printfn "Name: %s ID: %d" (employee.FirstName + " " + employee.LastName) (employee.EmployeeID))
  1. Specify a substring condition to the query by using the System.String.Contains(System.String) method. The following query returns all products that have "Chef" in their names. Also notice the use of System.Nullable<'T>.GetValueOrDefault(). The UnitPrice is a nullable value, so you must either get the value by using the Value property, or you must call System.Nullable<'T>.GetValueOrDefault().
query { 
  for product in db.Products do
  where (product.ProductName.Contains("Chef"))
  select product
} |> Seq.iter (fun product ->
                  printfn "ID: %d Product: %s" product.ProductID product.ProductName
                  printfn "Price: %M\n" (product.UnitPrice.GetValueOrDefault()))
  1. Use the System.String.EndsWith(System.String) method to specify that a string ends with a certain substring.
query {
  for product in db.Products do
  where (product.ProductName.EndsWith("u"))
  select product
} |> Seq.iter (fun product ->
                  printfn "ID: %d Product: %s" product.ProductID product.ProductName
                  printfn "Price: %M\n" (product.UnitPrice.GetValueOrDefault()))
  1. Combine conditions in a where clause by using the && operator.
// Open this module to use the nullable operators ?> and ?<.
open Microsoft.FSharp.Linq.NullableOperators

let salesIn1997 = query { 
  for sales in db.Category_Sales_for_1997 do
  where (sales.CategorySales ?> 50000.00M && sales.CategorySales ?< 60000.0M)
  select sales }

|> Seq.iter (fun sales ->
                printfn "Category: %s Sales: %M" sales.CategoryName (sales.CategorySales.GetValueOrDefault()))

The operators ?> and ?< are nullable operators. You can use a full set of nullable equality and comparison operators. For more information, see Linq.NullableOperators Module.

  1. Use the sortBy query operator to specify ordering, and use thenBy to specify another level of ordering. Notice also the use of a tuple in the select part of the query. Therefore, the query has a tuple as an element type.
printfn "Freight for some orders: "

query { 
  for order in db.Orders do
  sortBy (order.OrderDate.Value)
  thenBy (order.OrderID)
  select (order.OrderDate, order.OrderID, order.Customer.CompanyName)
} |> Seq.iter (fun (orderDate, orderID, company) ->
                  printfn "OrderDate: %s" (orderDate.GetValueOrDefault().ToString())
                  printfn "OrderID: %d Company: %s\n" orderID company)
  1. Ignore a specified number of records by using the skip operator, and use the take operator to specify a number of records to return. In this way, you can implement paging on data feeds.
printfn "Get the first page of 2 employees."

query { 
  for employee in db.Employees do
  take 2
  select employee
} |> Seq.iter (fun employee ->
                  printfn "Name: %s ID: %d" (employee.FirstName + " " + employee.LastName) (employee.EmployeeID)) 

printfn "Get the next 2 employees."

query { 
  for employee in db.Employees do
  skip 2
  take 2
  select employee
} |> Seq.iter (fun employee ->
                  printfn "Name: %s ID: %d" (employee.FirstName + " " + employee.LastName) (employee.EmployeeID))

Verifying the OData request

Every OData query is translated into a specific OData request URI. You can verify that URI, perhaps for debugging purposes, by adding an event handler to the SendingRequest event on the full data context object.

To verify the OData request

  • To verify the OData request URI, use the following code:
// The DataContext property returns the full data context.
db.DataContext.SendingRequest.Add (fun eventArgs -> printfn "Requesting %A" eventArgs.Request.RequestUri)

The output of the previous code is:
requesting http://services.odata.org/Northwind/Northwind.svc/Orders()?$orderby=ShippedDate&amp;$select=OrderID,ShippedDate

See Also

Query Expressions

LINQ Considerations (WCF Data Services)

ODataService Type Provider (F#)