In my The Evolution of a Query API post I outlined the details of our query API. In the end we ended up with the following:
List<Customer> customers = repository.FindAll(Customer.Columns.Age == 20 & Customer.Columns.Name == “foo”);
As I mentioned in our previous post the Customer.Columns “collection” is code generated off our database. The Customer class has a static Columns class that contains static properties for each of the columns defined in the Customer table.
public class Customer {
// class definition
public static class Columns {
public static StringColumn<Customer> Name = new StringColumn<Customer>(“Name”);
public static Column<Customer, int> Age = new Column<Customer, int>(“Age”);
}
}
We have a number of different Column types for different data types (StringColumn, DateColumn, etc.) that have custom methods on them that result in the creation of our Criteria objects. On our base column class we have the following static methods:
- Criteria EqualTo(T value);
- Criteria NotEqual(T value);
- Criteria LessThan(T value);
- Criteria LessThanOrEqual(T value);
- Criteria GreaterThan(T value);
- Criteria GreaterThanOrEqual(T value);
These utility methods allow us to do things like:
repository.FindAll(Customer.Columns.Name.EqualTo(“Steve”));
Our column classes also have operator overloads to provide the nice syntax that is shown in the initial sample. As you can imagine each of our operator overloads simply delegates to one of the above methods.
public static Criteria operator ==(T value) {
return EqualTo(value);
}
In order to chain our various criteria expressions together we implemented an operator overload on our base Criteria class for the & and | operators.
public static Criteria operator&(Criteria lhs, Criteria rhs) {
return new AndCriteria(lhs, rhs);
}
The final piece of the puzzle is writing a parser that can then analyze a criteria object and convert it to the correct string representation. In our case we’re usually converting it to SQL which our criteria class handles. Ideally when our evolution is a little further along we’ll have a separate class that handles the parsing and creation of the SQL so that we can then write other parsers for whatever OR Mapper we ultimately decide on.
public string ToSqlWhere(Criteria criteria) {
return “WHERE “ + GetColumnMapping(criteria.PropertyName) + “ “ + GetOperatorExpression(criteria) + “ “ + FormatValue(criteria.Value);
}
The above is a extremely simple contrived example that shows how one might write a “parser” that could handle converting a Criteria object to SQL. Obviously any real parser would have to have a lot more logic to handle AndCriteria’s, OrCriteria, and all the other conditions that are supported.
As I was chatting with Jeff Gonzalez tonight about how I implemented our query api he pointed me to this demo video of “The Last Component”. It appears that they’ve stolen every one of my ideas and then some.
Since they’re actually selling their framework it’s likely a lot more polished then what I have so have a look at their demo and grab their latest version to give it a spin.
As our query api continues to evolve I’ll post some additional details about its progress along with some more sample code for the various peices in the puzzle (Columns, Criterias, Parsers, and etc.). As always let me know what your interested in hearing more about.