We are looking for contributors, funding and sponsorship, so please get in touch!
Please contact joshcomley@gmail.com for more info.
Source code can be found at github.com/joshcomley/iql.
IQL stands for Intermediate Query Language. It is an adaptable middleware between your code and your data.
The core premise of IQL is converting lambda expressions through static analysis into an expression tree, which in turn can be converted into a query for any supported database. But built on top of that is much, much more.
C# / TypeScript / JavaScript > IQL > OData / in-memory / any DB
IQL has over 600 tests, and any new feature will always have a test written first.
The goal of IQL is to provide a clean, developer-friendly way of interacting with data, be it remote or local, using identical syntax in TypeScript/JavaScript and C#.
It was borne out of a requirement to provide a type-safe query language and CRUD utility in-code that can be directed at any data source, in multiple environments without ever needing to duplicate hand-written code.
// C#
var db = new AppDbContext();
// Getting a list of data
var clients = await db
.Clients
.Where(client => client.Name.Contains("abc"))
.Expand(client => client.People)
.OrderBy(client => client.Name)
.ToListAsync();
// Getting a specific entity
var someClient = await db.Clients.GetWithKeyAsync(123);
// Making changes
someClient.Name = "New name";
await db.SaveChangesAsync();
// Check for changes
var hasChanges = clientState.HasChanges;
// Monitor property changes
clientState.PropertyLocalValueChanged.Subscribe(e =>
{
var propertyState = e.Owner;
var propertyName = propertyState.Property.Name;
var oldValue = e.OldValue;
var newValue = e.NewValue;
});
// TypeScript
let db = new AppDbContext();
// Getting a list of data
let clients = await db
.Clients
.Where(client => client.Name.includes("abc"))
.Expand(client => client.People)
.OrderBy(client => client.Name)
.ToListAsync();
// Getting a specific entity
let someClient = await db.Clients.GetWithKeyAsync(123);
// Making changes
someClient.Name = "New name";
await db.SaveChangesAsync();
// Check for changes
let hasChanges = clientState.HasChanges;
// Monitor property changes
clientState.PropertyLocalValueChanged.Subscribe(e =>
{
let propertyState = e.Owner;
let propertyName = propertyState.Property.Name;
let oldValue = e.OldValue;
let newValue = e.NewValue;
});
The above query in the example to get a list of clients translates to this OData URI:
http://localhost/odata/Clients?$filter=contains($it/Name,'abc')&$expand=People&$orderby=$it/Name
The query in the example to get a specific client translates to this OData URI:
http://localhost/odata/Clients(123)
Saving changes via OData translates to submitting the appropriate DELETE, PATCH or PUT HTTP method.
OData functions and actions are also supported, and asynchronous methods are generated on the data context to enable easy calling.
IQL queries can also be generated completely dynamically:
// C#
var iql = new IqlDataSetQueryExpression
{
DataSet = new IqlDataSetReferenceExpression
{
Name = "Clients",
Kind = IqlExpressionKind.DataSetReference,
ReturnType = IqlType.Collection
},
EntityTypeName = "Client",
WithKey = new IqlWithKeyExpression
{
KeyEqualToExpressions = new List<IqlIsEqualToExpression>
{
new IqlIsEqualToExpression
{
Left = new IqlPropertyExpression
{
PropertyName = "Id",
Kind = IqlExpressionKind.Property,
ReturnType = IqlType.Unknown,
Parent = new IqlRootReferenceExpression
{
VariableName = "entity",
Kind = IqlExpressionKind.RootReference
}
},
Right = new IqlLiteralExpression
{
Value = 123,
InferredReturnType = IqlType.Integer,
Kind = IqlExpressionKind.Literal
},
Kind = IqlExpressionKind.IsEqualTo,
ReturnType = IqlType.Boolean
}
},
Kind = IqlExpressionKind.WithKey,
ReturnType = IqlType.Class
},
Parameters = new List<IqlRootReferenceExpression>
{
new IqlRootReferenceExpression
{
EntityTypeName = "Client",
InferredReturnType = IqlType.Unknown,
Kind = IqlExpressionKind.RootReference,
ReturnType = IqlType.Unknown
}
},
Kind = IqlExpressionKind.DataSetQuery,
ReturnType = IqlType.Class
};
// TypeScript
let iql = (() => {
let obj = new IqlDataSetQueryExpression();
obj.DataSet = (() => {
let obj = new IqlDataSetReferenceExpression();
obj.Name = "Clients";
obj.Kind = IqlExpressionKind.DataSetReference;
obj.ReturnType = IqlType.Collection;;
return obj;
})();
obj.EntityTypeName = "Client";
obj.WithKey = (() => {
let obj = new IqlWithKeyExpression();
obj.KeyEqualToExpressions = new Array<IqlIsEqualToExpression>(... [(() => {
let obj = new IqlIsEqualToExpression();
obj.Left = (() => {
let obj = new IqlPropertyExpression();
obj.PropertyName = "Id";
obj.Kind = IqlExpressionKind.Property;
obj.ReturnType = IqlType.Unknown;
obj.Parent = (() => {
let obj = new IqlRootReferenceExpression();
obj.VariableName = "entity";
obj.Kind = IqlExpressionKind.RootReference;;
return obj;
})();;
return obj;
})();
obj.Right = (() => {
let obj = new IqlLiteralExpression();
obj.Value = 123;
obj.InferredReturnType = IqlType.Integer;
obj.Kind = IqlExpressionKind.Literal;;
return obj;
})();
obj.Kind = IqlExpressionKind.IsEqualTo;
obj.ReturnType = IqlType.Boolean;;
return obj;
})()]);
obj.Kind = IqlExpressionKind.WithKey;
obj.ReturnType = IqlType.Class;;
return obj;
})();
obj.Parameters = new Array<IqlRootReferenceExpression>(... [(() => {
let obj = new IqlRootReferenceExpression();
obj.EntityTypeName = "Client";
obj.InferredReturnType = IqlType.Unknown;
obj.Kind = IqlExpressionKind.RootReference;
obj.ReturnType = IqlType.Unknown;;
return obj;
})()]);
obj.Kind = IqlExpressionKind.DataSetQuery;
obj.ReturnType = IqlType.Class;;
return obj;
})();
Because IQL supports multiple adapters at once, it can seamlessly change between online and an offline in-memory data store. Using this, it achieves support for offline working, and is able to synchronise data to the device or browser in-hand whilst online.
The data context (AppDbContext in the example) is generated using a tool that analyses your metadata (for example, your OData metadata URL) and produces your model classes and data context.
IQL isn't just a query language, but it leverages its linguistic prowess to allow extremely easy yet granular security configurations:
// C#
builder
.PermissionManager
.DefineEntityUserPermissionRule<ApplicationUser, ApplicationUser>(
"IsUserDisabled", _ =>
_.Entity.IsLockedOut
? IqlUserPermission.Full
: IqlUserPermission.None
);
// TypeScript
builder
.PermissionManager
.DefineEntityUserPermissionRule<ApplicationUser, ApplicationUser>(
"IsUserDisabled", _ =>
_.Entity.IsLockedOut
? IqlUserPermission.Full
: IqlUserPermission.None
);
The security can be configured to varying degrees of specificity, starting at a global level, through to specific entity types right down to configuring a permission for a specific property.
IQL can also run on the server using .NET and can enact any security configuration you have at the back-end.
IQL has a lot of features, and works on browsers, Node, .NET and mobile (via NativeScript or any web based mobile development package). There is a lot of work to do documenting IQL and some work preparing for general launch. If you are interested, please get in touch, support or try and submit any issues to github.com/joshcomley/iql.