Walkthrough: Accessing an OData Service by Using Type Providers (F#)

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 Introducing OData.

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 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.

    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)
    
  2. 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)
    
  3. 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))                         
    
  4. Specify a substring condition to the query by using the Contains method. The following query returns all products that have "Chef" in their names. Also notice the use of GetValueOrDefault. The UnitPrice is a nullable value, so you must either get the value by using the Value property, or you must call 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()))
    
  5. Use the EndsWith 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()))
    
  6. 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 }
    salesIn1997
    |> 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 (F#).

  7. 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)
    
  8. 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&$select=OrderID,ShippedDate

See Also

Tasks

Query Expressions (F#)

Reference

ODataService Type Provider (F#)

Other Resources

LINQ Considerations