
Traditionally, OData queries on data are expressed as simple strings without type checking during compilation or without IntelliSense support, in addition, the developer has to learn the syntax of the query language. This article describes the TsToOdata library, which turns queries into a convenient language construct and is applied similarly to classes and methods. You create typed queries using TypeScript keywords and familiar operators.
TsToOdata is a library for TypeScript. It is similar to LINQ for C #, but, unlike the latter, it is intended only for OData queries. For the developer who creates the queries, the most obvious part of TsToOdata is the query expression. Query expressions use declarative syntax, so the developer writes what needs to be done, without specifying how to do it. Using the query syntax, you can filter, organize, and group data from a data source, using a minimum amount of program code.
Install TsToOdata
npm install ts2odata
Creating a data model
First of all, we need to get the mapping of the OData schema to TypeScript classes.
The first step is to first get the schema from EDMX Json. You can use the OdataToEntity library for this .
IEdmModel edmModel;
using (var reader = XmlReader.Create("edmx_schema.xml"))
edmModel = CsdlReader.Parse(reader);
var generator = new OeJsonSchemaGenerator(edmModel);
using (var utf8Json = new MemoryStream())
{
generator.Generate(utf8Json);
utf8Json.Position = 0;
File.WriteAllBytes("json_schema.json", utf8Json.ToArray());
}
Json TypeScript. quicktype.
.
import { EntitySet, OdataContext } from 'ts2odata';
import * as oe from './order';
export class OrderContext extends OdataContext<OrderContext> {
Categories = EntitySet.default<oe.Category>();
Customers = EntitySet.default<oe.Customer>();
OrderItems = EntitySet.default<oe.OrderItem>();
OrderItemsView = EntitySet.default<oe.OrderItemsView>();
Orders = EntitySet.default<oe.Order>();
}
let context: OrderContext = OdataContext.create(OrderContext, 'http://localhost:5000/api');
context.Orders;
context.Orders.select(o => { return { p: o.Name } });
context.Orders.orderby(i => i.Id);
context.Orders.orderbyDescending(i => i.Id);
context.Orders.filter(o => o.Date.getFullYear() == 2016);
context.Orders.expand(o => o.Items);
context.Customers.expand(c => c.Orders).thenExpand(o => o.Items);
context.Orders.orderby(i => i.Id).skip(2);
context.Orders.orderby(i => i.Id).top(3);
context.OrderItems.groupby(i => { return { Product: i.Product } });
context.OrderItems.groupby(i => { return { OrderId: i.OrderId, Status: i.Order.Status } })
.select(g => {
return {
orderId: g.key.OrderId,
avg: g.average(i => i.Price),
dcnt: g.countdistinct(i => i.Product),
max: g.max(i => i.Price),
max_status: g.max(_ => g.key.Status),
min: g.min(i => i.Price),
sum: g.sum(i => i.Price),
cnt: g.count()
}});
context.Customers.key({ Country: 'RU', Id: 1 });
context.OrderItems.key(1, i => i.Order.Customer);
context.OrderItems
.select(i => {
return {
product: i.Product,
Total: i.Count * i.Price,
SumId: i.Id + i.OrderId
}
});
context.Orders.filter(o => o.Items.every(i => i.Price >= 2.1));
context.Orders.filter(o => o.Items.some(i => i.Count > 2));
IN
let items = [1.1, 1.2, 1.3];
context.OrderItems.filter(i => items.includes(i.Price), { items: items });
context.Orders.count();
asEntitySet
context.Orders(o => o.AltCustomer).thenSelect(o => {{
p1: o.Address,
: o.Country,
: o.Id,
: o.Name,
: o.Sex
}}).asEntitySet().orderby(o => o.Id)
GitHub.
, select, expand, groupby β β , , then: thenFilter, thenExpand, thenOrderby, thenOrderbyDescending, thenSkip, thenTop. select thenSelect , , , asEntitySet.
β filter/thenFilter, β select/thenSelect, β groupby , .
let count: number | null = null;
context.OrderItems.filter(i => i.Count == count, { count: count });
let s = {
altCustomerId: 3,
customerId: 4,
dateYear: 2016,
dateMonth: 11,
dateDay: 20,
date: null,
name: 'unknown',
status: "OdataToEntity.Test.Model.OrderStatus'Unknown'",
count1: 0,
count2: null,
price1: 0,
price2: null,
product1: 'unknown',
product2: 'null',
orderId: -1,
id: 1
};
context.Orders.filter(o => o.AltCustomerId == s.altCustomerId &&
o.CustomerId == s.customerId &&
(o.Date.getFullYear() == s.dateYear &&
o.Date.getMonth() > s.dateMonth &&
o.Date.getDay() < s.dateDay ||
o.Date == s.date) &&
o.Name.includes(s.name) &&
o.Status == s.status, s)
.expand(o => o.Items)
.thenFilter(i => (i.Count == s.count1 ||
i.Count == s.count2) &&
(i.Price == s.price1 ||
i.Price == s.price2) &&
(i.Product.includes(s.product1) ||
i.Product.includes(s.product2)) &&
i.OrderId > s.orderId &&
i.Id != s.id, s);
OdataFunctions.stringLength
context.Customers.filter(c => OdataFunctions.stringLength(c.Name) == 5);
OdataFunctions.arrayLength
context.Orders.filter(o => OdataFunctions.arrayLength(o.Items) > 2);
select, filter getQueryUrl toArrayAsync.
getQueryUrl URL . TypeScript:
let url: URL = context.Customers
.expand(c => c.AltOrders).thenExpand(o => o.Items).thenOrderby(i => i.Price)
.expand(c => c.AltOrders).thenExpand(o => o.ShippingAddresses).thenOrderby(s => s.Id)
.expand(c => c.Orders).thenExpand(o => o.Items).thenOrderby(i => i.Price)
.expand(c => c.Orders).thenExpand(o => o.ShippingAddresses).thenOrderby(s => s.Id)
.orderby(c => c.Country).orderby(c => c.Id).getQueryUrl();
OData :
http://localhost:5000/api/Customers?$expand=
AltOrders($expand=Items($orderby=Price),ShippingAddresses($orderby=Id)),
Orders($expand=Items($orderby=Price),ShippingAddresses($orderby=Id))
&$orderby=Country,Id
toArrayAsync Json. TypeScript:
context.Customers
.expand(c => c.Orders).thenSelect(o => { return { Date: o.Date } }).orderby(o => o.Date)
.asEntitySet().select(c => { return { Name: c.Name } }).orderby(c => c.Name).toArrayAsync();
Json:
[{
"Name": "Ivan",
"Orders": [{
"Date": "2016-07-04T19:10:10.8237573+03:00"
}, {
"Date": "2020-02-20T20:20:20.000002+03:00"
}
]
}, {
"Name": "Natasha",
"Orders": [{
"Date": "2016-07-04T19:10:11+03:00"
}
]
}, {
"Name": "Sasha",
"Orders": []
}, {
"Name": "Unknown",
"Orders": [{
"Date": null
}
]
}
]
, , toArrayAsync OdataParser:
import { OdataParser } from 'ts2odata';
import schema from './schema.json';
let odataParser = new OdataParser(schema);
context.Orders.toArrayAsync(odataParser);
(enum)
OData (Namespace), :
let context: OrderContext = OdataContext.create(OrderContext, 'http://localhost:5000/api', 'OdataToEntity.Test.Model');
In some cases, it may be necessary to create an OdataParser object to properly translate the enumerations .
import { OdataParser } from 'ts2odata';
import schema from './schema.json';
let odataParser = new OdataParser(schema);
let context: OrderContext = OdataContext.create(OrderContext, 'http://localhost:5000/api', 'OdataToEntity.Test.Model', odataParser);
Source
The source code is on GitHub .
In the source folder - the Node code of the package, in the test folder - tests.
I hope my TsToOdata project will be useful to you and save you from the routine of writing monotonous code.