IQL is an open source ORM (Object Relational Mapper) written in C# and TypeScript*.

* the code-base is written in C#, but all code and tests are converted to TypeScript automatically, keeping development DRY.

It works in browsers, Node, .NET and mobile (where JavaScript/TypeScript can be used, for example NativeScript, React Mobile or Cordova).

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? Huh?

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

Test Driven

IQL has over 600 tests, and any new feature will always have a test written first.

Goal

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.

Example

    // 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;
});

  

IQL currently has adapters for an in-memory database and OData, however many more are planned, help welcome!

OData

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.

Dynamic

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;
})();
  

Offline

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.

Context

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.

Security

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.

Server

IQL can also run on the server using .NET and can enact any security configuration you have at the back-end.

Much more

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.