Coder Social home page Coder Social logo

cs-odata-client's Introduction

C# OData Client

var result = await oDataContext.Products
    .Select(x => new { x.ProductID, x.ProductName })
    .Filter(x => x.ProductID < 10)
    .Expand(r => r.Order_Details.Select(o => o.OrderID )
            .Expand(r => r.Order.Select(o => new { o.ShipCity, o.ShipAddress, o.ShipName, o.ShipRegion })
                .Expand(r => r.Employee.Select(e => $"{e.FirstName} {e.LastName}"),
                    (r, e) => new { Order = r, Employee = e })
                .Expand(r => r.Shipper.Select(s => s.CompanyName),
                    (r, s) => new { r.Employee, r.Order, Shipper = s }),
                (d, o) => o
            ),
        (a,b) => new { Order_Details = a, Orders = b }
    )
    .Expand(r => r.Category.Select(s => s.CategoryName), 
        (r, c) => new { r.Order_Details, r.Orders, Category = c })
    .Orderby(p => p.Supplier.City)
    .ExecuteAsync();

An example query

Table of contents

Architectural overview

This client aims to provide a convenient and type-safe way to integrate an OData source into your project. For this the library comes with a code generator that generates all classes and boilerplate for you. The query builder itselfs is strongly inspired by Entity Framework and uses Linq expressions to make the OData queries as close to normal C# as possible.

This means that the client heavily relies on the Expression type and uses various Linq-to-OData compilers for the different parts of the queries. The client provides a complete end-to-end integration of OData, meaning that it doesn’t only allow you to build queries but takes care of execution and deserialisation. To make the HTTP requests needed to run a query the ODataContext depends on a IHttpClientFactory. This can be added to your services with services.AddHttpClient();. The deserialisation happens by reflection and composition of the lambda’s provided when building the query.

Outside of this complete package the library also exposed an API to generated parts of an OData query. Those are the FilterExpression, SelectExpression and OrderbyExpression classes who all expose a static Compile method that can be used to compile individual lambda’s to their OData counterparts.

Building queries

The building of queries starts of with an instance of the ODataContext, as generated by the code generator.

Select

With Select you can select data from the odata source by providing a lambda. From this function the referenced attributes will be extracted and they will be included in the $select part of the OData query. The function itself will be run as the first part of the deserialization process. Inside the lambda you can use any language construct that you want.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName });

Filter

With Filter you can add a filter to the OData query. Filter expressions do get compiled into an OData expression. Therefore, not all C# language constructs are allowed. Using an unsupported construct will result in an exception. Supported constructs are:

  • member access on the argument
  • referencing or invoking anything that isn’t on the argument, this will be converted to the resulting value
  • binary operators: most of the algebraic, logical and comparison operators
  • unary operators: negation, not, incrementation and decrementation
  • constants
  • DateTime operations: the magic get accessors of C# do get compiled to functions in OData. Supported are: Day, Hour, Minute, Month, Second and Year
  • String methods: ToLower, ToUpper, Trim, Contains, Length, Substring, StartsWith, EndsWith, IndexOf and Replace
  • Math methods: Round, Floor and Ceiling
var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .Filter(x => x.ProductID < 10 && x.Category.CategoryName.Contains("Foo"));

Expand

With Expand you can add an $expand to your query. Expands come in two flavours: expands to a collection or to a single object (e.g. to-1 and to-n relations). There are two things needed for an expansion: the inner query and the merger function.

The inner query is constructed in a lambda from the relations builder to a query. In this query you can use any OData operator that is supported by the type of relation.

The merger function takes the result of the query up until now and the result of the inner query as arguments and returns the new combined result. Think of this as the last argument of Join in Entity Framework. This merger function is only used in the deserialization process and does not get compiled. And C# code is allowed in here. You should account for null-values in the second argument in case of optional relations.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .Expand(r => r.Order_Details.Select(o => o.OrderID),
        (a,b) => new {Order_Details = a, Orders = b}
    );

Orderby

With Orderby you can order the result. This method accepts a lambda from the type of the source to the attribute that should be sorted on.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .Orderby(x => x.Supplier.City);

OrderbyThen

With OrderbyThen you can add an additional order to the result. This method accepts a lambda from the type of the source to the attribute that should be sorted on.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .OrderbyThen(x => x.Supplier.City);

OrderbyDescending

With OrderbyDescending you can order the result descending. This method accepts a lambda from the type of the source to the attribute that should be sorted on.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .OrderbyDescending(x => x.Supplier.City);

OrderbyDescendingThen

With OrderbyDescendingThen you can add an additional descending order to the result. This method accepts a lambda from the type of the source to the attribute that should be sorted on.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .OrderbyDescendingThen(x => x.Supplier.City);

Top

With Top you can set the $top of the query.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .Top(10);

Skip

With Skip you can set the $skip of the query.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .Skip(10);

ExecuteAsync

ExecuteAsync runs the query and returns the value.

var q = await oDataContext.Products
    .Select(x => new{ x.ProductID, x.ProductName })
    .ExecuteAsync(10);

Code generation

In the DTF there is a command present that allows you generate a context via the CLI. See here

dotnet run cs-odata -u "https://services.odata.org/V4/Northwind/Northwind.svc" -o "ODataContext.cs"

With the SchemaCodeGenerator you can generate an ODataContext from a XML schema. The Generate method expects an url to the $metadata and a SchemaCodeGeneratorOptions object. The options options are:

SelectOnRelations

Values: None, ToOne or All.

Sets if (and which) relations of an entity can be referenced in the Select lambda.

not yet implemented

FilterOnRelations

Values: None, ToOne or All.

Sets if (and which) relations of an entity can be referenced in the Filter predicate. OrderbyOnRelations Sets if (and which) relations of an entity can be referenced in the Orderby lambda.

Compilation API

Next to the end-to-end querybuilder this library also provides an API to build parts of an OData query. With the FilterExpression, SelectExpression and OrderbyExpression classes you can compile C# lambda’s into various OData expressions for usages in custom clients.

var select = SelectExpression.Compile<MyType>(x => new { x.Foo, x.Bar });
var filter = FilterExpression.Compile<MyType>(x => x.Foo.StartsWith("baz") && x.Bar > 2);
var orderby = OrderbyExpression.Compile<MyType>(x => x.Bar);

cs-odata-client's People

Contributors

wimjongeneel avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.