When writing a custom LINQ provider, developer must focus into plenty of issues other than the main purpose of just creating a LINQ to something. Like how to deal with projection, how to parse valued expressions, how to parse members, how to do orderby and the list goes on.
This is the reason I came up with LinqExtender, it deals with all such issues. But over the time I found out that the idea of providing a fluent interface to lookup your query information is easy at some point but becomes really cluttered as the provider logic goes bigger and thus it greatly violates SRP (Single responsibility pattern). Over all, it inherits a base class that makes even impossible to include my own base. With all these in mind I came to push a new release that cuts all this issues.
Therefore, first rather having an iterative fluent interface, it is build on simplified AST (Abstract syntax tree) over the original System.Linq.Expressions with an intent more on making of custom LINQ providers easy, contextual, elegant and of course free of noises like how to deal with projection, what to be done if user invokes a method on the IQueryable, etc.
Moreover, as I don’t want to implement a base class, therefore just an interface should be sufficient enough to make the class queryable.
public interface IQueryContext<T>
IEnumerable<T> Execute(Ast.Expression expression);
Now, if we consider the following LINQ query:
var query = from book in context
where (book.Id > 1) && (book.Author == "Scott" || book.Author == "Charlie")
Things that come to mind on the first place are:
- How do translate the logical groupings
- How to deal with different opeartors
- How to get the value that is compared for a member
Or if the where clause has expression like: book.Author == GetCurrentUsername(), we need to do some specific arts to invoke and reflect the value out of it. Will it not be nice, if the value is already there for us as intended. LinqExtender was/is focused to do all that for you.
The above expression is roughly translated into a simplified expression tree that looks similar to:
TypeExpression : Name == "Book", If NameAttribute applied then Name = "as specified".
LogicalExpresion - Contains the logical parts | Operator = LogicalOperator.AND
Name == "Id"
Value = 1
LogicalExpression : Operator = LogicalOperator.OR
Name == "Author"
Value = "Scott"
Name == "Author"
Value = "John"
The root is the BlockExpresion, it contains other expressions as specified in the query. Like in this case TypeExpression is for select, it is possible to get the TypeReference for the target object. LambdaExpression is for where clause, it can contain logical and/or binary expressions. LogicalExpresion on the other hand can contain N number of binary expressions and each BinaryExpression contains MemberExpression with the detail of the member followed by a LiteralEpxression with the reflected value from the query.
If we had an orderby clause in the expression, then BlockExpression would have contained an additional OrderByExpression.
Finally, visiting the expression (using Visitor Pattern) will produce a nice TSQL (Showed in the sample text provider => project homepage):
select * from Book
Book.Id > 10 && (Book.Author = "Scott" OR Book.Author = "Charlie")
The is just an overview, i have pulled up a home page for the project (shows creating a custom text provider that prints out TSQL representation of the query to the console) along with link to the source and archive to download. You can find it here:
However, I wont say that the tool is a feature complete, therefore feel to fork, update and use it as you like.
Here to mention that I moved the project from CodePlex(linqextender.codeplex.com) (that served it well and good), as github gives me more simplicity on how I manage the project and gives a way to pull up my own project homepage (which is also a GIT repository). Not to mention that I pulled the home page with Jekyll in no time with a minimalistic look and feel and of course its HTML 5.