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
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)
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)
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))
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()))
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()))
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#).
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)
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
Reference
ODataService Type Provider (F#)