Click here to Skip to main content
15,867,704 members
Articles / Operating Systems / Windows

A Custom linq query builder

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
19 Sep 2009CPOL3 min read 30.4K   9   4
In my opinion one the most interesting features of Linq is Expression Trees.They let us build structured queries over IQueryable type.IQueryable ,as its name implies, is an interface that let us build a query over a provided object of type T.Although IQueryable and expression trees are mostly u
In my opinion one the most interesting features of Linq is Expression Trees.They let us build structured queries over IQueryable<t> type.
IQueryable<t> ,as its name implies, is an interface that let us build a query over a provided object of type T.Although IQueryable and expression trees are mostly used in Linq to sql but since every IEnumerable<t> can be converted to an IQueryable object (using System.Linq.AsQueryable method) these great tools are available to every kind of Linq (linq to Xml and linq to objects for example).
When we build an expression tree over an IQueryable object , the expression itself won't be executed until we really want to get the data.
For example if we write the following code:
(DataSource is an IQueryable<post> object)

var result=DataSource.Where(post=>post.Title.IndexOf("Linq")>0);



result is yet another IQueryable<post> that you can query again :

result=result.Where(post=>post.CreationDate>new DateTime(2009,1,1));



Now if we've added all we wanted to our query we can execute it either by converting the result to an ICollection (a List or an array for example) or by executing GetEnumrator method ( usually by using a for each loop).

Based on this introduction I designed a custom query builder that facilitate querying an object using collected search arguments (in a windows /web form for example)

Here's the scenario that I had in mind :
There's a web/win form that lets a user fill-in some information about what he wants to see.
Consider a blog application , a user can search post based on their titles and/or their creation date.Therefore a QueryPostArgs object is created and populated using the information provided by user.
Then this object is passed to a QueryBuilder to build a query from it.Query builder contains some register query methods and when is asked to build the query it will use them to build the final query.
Enough talking let write some code :

To collect and transfer the arguments information we have an AbstractQueryArgs class.

public abstract class AbstractQueryArgs<t> where T :class
{
}



To build queries over a post object we then derived from it and create a PostQueryArgs class.

public class PostQueryArgs : AbstractQueryArgs<post>
 {
     public PostQueryArgs()
     {
         CreationDateFrom = DateTime.MinValue;
         CreationDateTo = DateTime.MaxValue;
     }
     public string Title { get; set; }
     public DateTime CreationDateFrom { get; set; }
     public DateTime CreationDateTo { get; set; }
     public string[] Tags { get; set; }
 }


To build query using an AbstractQueryArgs we have an AbstractQueryBuilder like this:

public abstract class AbstractQueryBuilder<t,tsearchargs> 
where T :class where TSearchArgs :AbstractQueryArgs<t>
 {
     protected delegate IQueryable<t>
     QueryMethodDelegate
     (IQueryable<t> queryable,TSearchArgs searchArgs);

     public IQueryable<t> DataSource { get; private set; }
     private readonly List<QueryMethodDelegate>
     _queryMethodDelegates = new List<QueryMethodDelegate>();

 
     protected AbstractQueryBuilder(IQueryable<t> dataSource)
     {
         DataSource = dataSource;
     }

 
     protected void AddQueryMethod(QueryMethodDelegate queryMethodDelegate)
     {
         _queryMethodDelegates.Add(queryMethodDelegate);
     }
     public IQueryable<t> Build(TSearchArgs searchArgs)
     {
         var queryable = DataSource
         foreach (var queryMethodDelegate in _queryMethodDelegates)
         {
             queryable=queryMethodDelegate(queryable,searchArgs);
         }

         return queryable;
     }
 }


It allow us to build custom query builders for any T class.As you can see it gets an IQueryable <t><t><t><post><post> as its data source via constructor.Then we can add some query methods to a list of query methods . A query method is any method that gets an IQueryable and a query arguments object and returns an IQueryable (It somehow a visitor method that visits an IQueryable object and do something on it based on provided query arguments and return it back ) To define a query method a protected delegate is defined.Every class that is derived from this abstract class can add its custom query methods to the query methods list using AddQueryMethod protected method.
Finally there's this Build method that build an IQueryable object using the provided datasource and passing it to each query method.As you can see this method is also returning an IQueryable<t> object that lets us to perform any further queries over it if necessary.

Now we can create our custom query builder for our post object as follows:

public class PostQueryBuilder:AbstractQueryBuilder<post,postqueryargs>
 {
     public PostQueryBuilder(IQueryable<post> dataSource) : base(dataSource)
     {
         AddQueryMethod(AddTitleExpression);
         AddQueryMethod(AddCreationDateExpression);
         AddQueryMethod(ContainsAllTags);
     }

     private static IQueryable<post>
     ContainsAllTags(IQueryable<post> queryable , PostQueryArgs queryArgs)
     {
         if (queryArgs.Tags == null || queryArgs.Tags.Length == 0) return queryable;
         return queryable.Where
         (x => x.Tags.Intersect(queryArgs.Tags).Count() == queryArgs.Tags.Length);
     }

     private static IQueryable<post>
     AddTitleExpression(IQueryable<post> queryable,PostQueryArgs queryArgs)
     {
         return string.IsNullOrEmpty(queryArgs.Title) ? queryable :
         queryable.Where(x=>x.Title.IndexOf(queryArgs.Title)!=0);
     }

     private static IQueryable<post>
     AddCreationDateExpression(IQueryable<post> queryable,
                               PostQueryArgs queryArgs)
     {
     
         return queryable.Where(
             x => x.CreationDate >= queryArgs.CreationDateFrom &&
                  x.CreationDate <= queryArgs.CreationDateTo);        
     }


As you can see this class add three query methods to query methods list.One for title,one for creation date and one for tags.

Now that we have created these classes , all we need to do to build a query on a post datasource is like this:

var posts=new []{_post1,_post2,_post3};
var queryBuilder=new PostQueryBuilder(posts);

var queryArgs=new PostQueryArgs{Title="Intro",Tags=new []{"C#","Linq"}};
var query=queryBuilder.Buid(queryArgs);



Then we have a IQeryable<post> object that when we try to enumerate it , post objects with "Intro" in their title and containing "C#" and "Linq" tags will be returned.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Iran (Islamic Republic of) Iran (Islamic Republic of)
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralUse In Rules Engine Pin
Trooks21-Sep-09 13:34
Trooks21-Sep-09 13:34 
QuestionCode? Pin
tonyt19-Sep-09 10:53
tonyt19-Sep-09 10:53 
AnswerRe: Code? Pin
beatles169219-Sep-09 20:48
beatles169219-Sep-09 20:48 
GeneralRe: Code? Pin
Robeir Ampeir19-Apr-10 21:57
Robeir Ampeir19-Apr-10 21:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.