Problem Solved: Generating Dynamic CAML Queries 

Tags:

I've turned the process of writing CAML queries into a piece of cake.
 
The solution is a very simple helper library that has zero parsing/conversion code that could go wrong, and it's all based on a simple idea (more on that later). 
 
The solution is especially useful if you need to generate a CAML query dynamically.  This is where conventional tools like the U2U CAML Query Builder fail.
 
By dynamic CAML query, I mean that the nature of the query (the combination of fields that will be used) is not known at compile time.  This is usually the case when it is up to the users to select the filter/query criteria that they want to use.
 
The idea of a dynamic CAML query has huge applications in reporting for almost any type of SharePoint application (so long as there is data to be found in SharePoint lists or document libraries).  By eliminating the difficulties in creating CAML XML queries dynamically, it becomes relatively easily to create a simple "advanced search"-like form that users can use to view a roll-up of all items that match their given criteria.  This roll-up report or view would be real-time and accurate in that it would have zero dependencies on your search crawl schedules or search configuration.
 
An example is necessary to clarify.  Suppose I wanted to find all default.aspx pages and Word documents that were modified today and created within this year.  Sounds simple enough, right?  The CAML query XML might look like this:
 
<And>
  <And>
    <Eq>
      <FieldRef Name="Modified" />
      <Today />
    </Eq>
    <Gt>
      <FieldRef Name="Created" />
      <Value Type="DateTime">2008-01-01T00:00:00Z</Value>
    </Gt>
  </And>
  <Or>
    <Eq>
      <FieldRef Name="Name" />
      <Value Type="String">default.aspx</Value>
    </Eq>
    <Contains>
      <FieldRef Name="Name" />
      <Value Type="String">.doc</Value>
    </Contains>
  </Or>
</And>
 
(I realize the example isn't exactly something very practical but please bear with it.  I'm also quite sure you can check the file extension using a more specific FieldRef but I wanted to show "Contains").
 
With my solution, it's a matter of writing the following C#:

Caml camlQuery =
  
Caml.FieldRef("Modified") == Caml.Today
  
& Caml.FieldRef("Created") > Caml.Value(new DateTime(2008, 1, 1))
  
& (Caml.FieldRef("Name") == Caml.Value("default.aspx") | Caml.FieldRef("Name").Contains(".doc"));

To get the current XML string for the camlQuery object (e.g., for use with SPQuery or SPSiteDataQuery), simply use camlQuery.OuterXml or camlQuery.GetFormattedString().

For one thing, the above C# code is much easier to read and write than the corresponding CAML XML.  In addition, you can add clauses dynamically as necessary (e.g., depending on whether a textbox has a value or not).  As a bonus, you get Intellisense and there is little possibility of a typo.

If you've ever tried to build a CAML query dynamically in a manual way, you probably quickly realized that you were getting nowhere fast and it may have seemed like you had a very complex problem to tackle.
 
This problem is the direct result of the way CAML uses the fast-to-process but unintuitive prefix notation instead of the infix notation that is natural to human beings.  In other words, we humans find it awkward to state a logical (or mathematical) operator before its two operands.  We would rather say "Cat OR Dog" and "3 + 4" instead of OR(Cat, Dog) and Add(3,4).  It starts to get more and more absurd for us when the result of an operation is the argument of another -- consider the ease with which we can write and understand
(8 + 2)/5 x 4
versus
Multiply(Divide(Add(8, 2), 5), 4).
There is a systematic way to take your query from an intuitive infix representation to CAML's prefix form and you knowingly or unknowingly do this conversion when you need to write a CAML query, but what if you're not there to write the query XML when it's needed?  What if your code has to generate the query XML based on specified web part properties or based on the user's submission of an advanced search form?
 
Side note: I am not saying that CAML should have been written to use infix like SQL.  Computers like to know the operator (function) first and then the operands (arguments) second, and when you write infix you just create a lot more work for the computer.  How would you accomodate infix with XML? You really can't.  And if you didn't use XML, the computer probably have to be a lot of manual string parsing.  Finally, in the end the computer probably needs everything converted to the function-followed-by-arguments form and so it needs additional (unnecessary) work to get there.
 
So does my solution convert infix strings to postfix CAML XML?  Actually, no.  As I said, there is zero parsing code involved and the solution is much simpler than that and also less error prone.  All I do is simply create a class called Caml that inherits from XmlElement and I override all of the binary operators (==, <, >, <=, &, |, etc) so that they take Caml objects as arguments and return a 3rd new Caml object (which is, of course, just more Xml).  I let the C# compiler do its magic that allows you to write a binary operator like '<' between its two arguments.  Then you can combine all of the clauses and logical operators in your query just as intuitively as you write an "if" statement.
 
See Nadeem.SharePoint.zip (in the Documents library of this blog site) for the code.  There are two flavors available: the initial version that I wrote very quickly as a proof-of-concept using the Nadeem.SharePoint namespace (in which all code is in one simple .Cs file), and a version using the Infusion.SharePoint namespace that was very nicely refactored, commented and cleaned up by Szymon Rozga, a colleague of mine at Infusion Development.
 
Side note: I realize that LINQ 2 SharePoint (formerly LINQ 2 CAML) addresses difficulties associated with writing complex CAML queries, but not everyone is able to deploy .NET 3.5.  Moreover, I am not sure how easy it is to create dynamic/ad-hoc LINQ queries (I admittedly do not have any LINQ experience).
 
Posted by Nadeem Mitha on 21-Mar-08
1 Comments  |  Trackback Url  |  Link to this post | Bookmark this post with:        
 

Links to this post

Comments


Good commented on Friday, 16-May-2008
Good this Post

Name:
URL:
Email:
Comments: