Linq in Javascript - Queryables, Expression Trees, and OData

Over the last year I have been writing some exciting javascript frameworks. One of which was an ORM in javascript. While writing this ORM, my boss didn't like the fact that we were doing this.

dataContext.people.where("?$filter=FirstName eq 'Jared'");

 He said that this wouldn't save us if we changed the way we queried our service. He was implying that we needed a way to abstract away what we wanted to query through some kind of class. And he was right. My brain got super excited, and thought that this would be a great problem to solve. I remembered studying how C# created Linq. How they use expression trees to abstract the query into a set of expressions. So I knew that I wanted to use expressions trees, but how do I make them?

Linq builds these expressions with lambdas. I personally believe that lambda expressions read really well, and is easy to follow. It also creates a unified way to query List's, Databases and any collection that you need to access. After that point, I knew what I wanted, and I wasn't going to stop until I had it.

So now we query our people collection like this:

dataContext.people.where(function(p){ 
    return p.firstName.equals("Jared");
}).toArray().then(function(array){
    console.log(array);
});

So what happens here is that we create a Queryable of Type Person. The Queryable has a where method on it. That where method creates, what we call an ExpressionBuilder, and passes it into the callback as "p". So the "p" in the callback really isn't a person at all, its an ExpressionBuilder that has all the properties of a Person. So through this ExpressionBuilder we build a "where expression". Now from that expression tree built by the Expression Builder we are able to build an OData string to query our service. Isn't that great. :)

Here are some things that you can do.

dataContext.people.where(function(p){ 
    return p.firstName.equals("Jared");
}).orderBy(function(p){
    return p.firstName;
}).orderByDesc(function(p){
    return p.age;
}).take(5).skip(3).toArray().then(function(array){
    ///...Do something great!
});

The good news is that I work for a great company, and they believe in open source, and they have allowed me to open source this code. You can find the code here.

I really need to write a longer post about how to write your own Query Provider. If you are interested in how to write a Query Provider look at the ODataProvider.html on github. But for now I'll just show what you can do.

Here I make a Queryable and pass it to the odata object's toString method to make the queryable into a string.

var Person = function(){
    this.firstName = null;
    this.lastName = null;
    this.age = null;
};

var queryable = new BoostJS.Queryable(Person);

queryable = queryable.where(function(p){
    return p.firstName.equals("Jared");
});

var odataString = BoostJS.odata.toString(queryable); 

console.log(odataString);    //--> &$filter=((FirstName eq 'Jared'))

In order to really leverage the power of queryables, we need to treat all collections like they are queryable. So I have created a way to convert an array into a queryable.

var Person = function(){
    this.firstName = null;
    this.lastName = null;
    this.age = null;
};

var jared = new Person();
var justin = new Person();

var array = [jared, justin];

var queryable = array.asQueryable(Person);


Here are most of the ways that you can use it



//...

queryable.where(function(p){
   return p.firstName.equals("Jared");
});

//...

queryable.where(function(p){
   return p.age.greaterThan(30);
});

//...

queryable.where(function(p){
    return p.age.lessThan(30);
});

//...

queryable.where(function(p){
    return p.age.greaterThanOrEqualTo(30);
});

//...

queryable.where(function(p){
    return p.age.lessThanOrEqualTo(30);
});

//...

queryable.where(function(p){
    return p.lastName.substring("are");
});

//...

queryable.where(function(p){
    return p.lastName.substringOf("arn");
});

//...

queryable.where(function(p){
    return p.firstName.startsWith("Jar");
});

//...

queryable.where(function(p){
    return p.firstName.endsWith("red");
});

//...

queryable.where(function(p){
    return p.firstName.endsWith("red");
}).skip(1);

//...

queryable.where(function(p){
    return p.firstName.endsWith("red");
}).take(5);

//...


queryable.where(function(p){
    return p.lastName.substringOf("arn");
}).orderBy(function(p){
    return p.age;
}).orderByDesc(function(p){
    return p.lastName;
});

//...

// You can also use "and" and "or" operators.
queryable.where(function(p){
    return this.and( p.lastName.equals("Barnes"), p.firstName.equals("Jared") );
});

// More Complicated
queryable.where(function(p){
    return this.or(this.and( p.lastName.equals("Barnes"), p.firstName.equals("Jared") ), p.age.greaterThan(50));
});


This isn't perfect yet, but I think it has enough to offer right now to benefit lots of companies using .NET's Web API with OData.

I really hope this opens your possibilities as it did ours. Enjoy!

Find Linq for javascript here.