Friday, April 2, 2010

Camlex.NET 2.0 for Sharepoint released

I’m glad to announce that next version of Camlex.NET project is released. About 2 months ago I introduced release of Camlex.NET on codeplex. During this time we received feedback from community and took into account applied developers needs when worked over 2.0 version. Yesterday we published Camlex.NET 2.0. In this post I will describe new features and differences from previous version.

1. Dynamic filtering conditions

This is the most excited feature added in 2.0 version. In every day Sharepoint development we often need to build CAML query based on predefined set of values. E.g. we need to search items which have Ids contained in array {1, 2, 3} (analog of IN operator in SQL), or we need to retrieve all items which contains one of the substrings (or all of them) in their Title { “hello”, “world” }. This was not easy with classical approach of building CAML queries based on strings. It was also not so easy with Camlex.NET 1.0: I showed example how to build dynamic expression based on set of values and pass it to Camlex in one of my previous posts. Nevertheless it required extra “infrastructure” work from developer. By infrastructure I mean those work which is not related directly with business task – to retrieve items which match some conditions. In version 2.0 we addressed this issue and added 2 additional methods in IQuery interface WhereAll and WhereAny (by analogy with All and Any methods in Linq):

   1: public interface IQuery
   2: {
   3:     IQuery Where(Expression<Func<SPListItem, bool>> expr);
   4:     IQuery WhereAll(IEnumerable<Expression<Func<SPListItem, bool>>> expressions);
   5:     IQuery WhereAny(IEnumerable<Expression<Func<SPListItem, bool>>> expressions);
   6:     ...
   7: }

Method Where exists from 1.0 version. I showed it here to compare signatures of new methods with old one. The main difference is that WhereAll and WhereAny methods receive list of lambda expressions (or more accurate – IEnumerable) in contrast to Where method which receives single lambda expression.

So what these methods do? As I said they were named by analogy with Linq methods. And by analogy with them WhereAll method constructs CAML query for retrieving those items which satisfy all conditions specified in argument. In other words WhereAll method contructs CAML query using <And> logical join (see And element). WhereAny method returns CAML query which can be used to retrieve items which satisfy at least one of specified conditions – it uses <Or> logical join (see Or element).

Lets see examples. Suppose that we need to retrieve all items which contain at least one of the values {“hello”, “greeting”, “hi”} in Title field. I.e. we need to use the following CAML query:

   1: <Where>
   2:   <Or>
   3:     <Or>
   4:       <Contains>
   5:         <FieldRef Name="Title" />
   6:         <Value Type="Text">hello</Value>
   7:       </Contains>
   8:       <Contains>
   9:         <FieldRef Name="Title" />
  10:         <Value Type="Text">greeting</Value>
  11:       </Contains>
  12:     </Or>
  13:     <Contains>
  14:       <FieldRef Name="Title" />
  15:       <Value Type="Text">hi</Value>
  16:     </Contains>
  17:   </Or>
  18: </Where>

That’s how it can be done in Camlex.NET 2.0:

   1: // list of tokens
   2: var tokens = new List<string> { "hello", "greeting", "hi" };
   3: var expressions = new List<Expression<Func<SPListItem, bool>>>();
   4:  
   5: // create lambda expression for each token in list
   6: foreach (string t in tokens)
   7: {
   8:     string token = t;
   9:     expressions.Add(x => ((string)x["Title"]).Contains(token));
  10: }
  11:  
  12: // prepare query
  13: string query = Camlex.Query().WhereAny(expressions).ToString();

So idea is quite simple – create list of lambda expressions and pass this list into Camlex. It will join expressions using And or Or joins – so it will have one big expression as result and pass it in old Where method which you familiar with from 1.0 version.

Notice that you can create various expressions – i.e. not necessary to create the same expression for each value. You can even provide different argument names in expressions – Camlex doesn’t restrict you to have the same argument names for all provided expressions. For example we need to select item which have ID = 1 and which Title = “Hello world”:

   1: <Where>
   2:   <And>
   3:     <Eq>
   4:       <FieldRef Name="ID" />
   5:       <Value Type="Integer">1</Value>
   6:     </Eq>
   7:     <Eq>
   8:       <FieldRef Name="Title" />
   9:       <Value Type="Text">Hello world</Value>
  10:     </Eq>
  11:   </And>
  12: </Where>

It can be done using the following code:

   1: var expressions = new List<Expression<Func<SPListItem, bool>>>();
   2: expressions.Add(x => (int)x["ID"] == 1);
   3: expressions.Add(y => (string)y["Title"] == "Hello world");
   4:  
   5: string query = Camlex.Query().WhereAll(expressions).ToString();

As you can see Camlex may successfully translate queries with different arguments names “x” and “y”.

2. Search by field Ids

Camlex 1.0 allowed only to search items using field Title. But often developers use field Id when prepare CAML query. In Camlex.NET 2.0 it is available now. Let see how to search items which have Title = “Hello world” using field Id of standard Title field:

   1: <Where>
   2:   <Eq>
   3:     <FieldRef ID="fa564e0f-0c70-4ab9-b863-0177e6ddd247" />
   4:     <Value Type="Text">Hello world</Value>
   5:   </Eq>
   6: </Where>

The following code produces this CAML query:

   1: string query = Camlex.Query().Where(
   2:     x => (string) x[SPBuiltInFieldId.Title] == "Hello world").ToString();

SPBuiltInFieldId.Title is OTB property of System.Guid type. You can also provide your own variable of Guid type in indexer.

One drawback is that with this feature we’ve lost drawback compatibility with 1.0 version. In previous version we used SPItem in all expressions which have only 2 indexers with int and string arguments. We changed SPItem –> SPListItem which has additional indexer with System.Guid parameter. It became a breaking change in 2.0 version.

3. Search by lookup id and lookup value

In CAML you can search items by lookup value and lookup id using undocumented LookupId attribute  of <FieldRef> element: see comments to FieldRef Element MSDN article. Now it is also available in Camlex.NET 2.0. For example there is “Status” lookup field in some list and we need to retrieve those items which have Status = “Completed”. We can do it either using lookup value:

   1: <Where>
   2:   <Eq>
   3:     <FieldRef Name="Status" />
   4:     <Value Type="Lookup">Completed</Value>
   5:   </Eq>
   6: </Where>

or using lookup id (suppose that look up item which has Title = “Completed” has ID = 1):

   1: <Where>
   2:   <Eq>
   3:     <FieldRef Name="Status" LookupId="True" />
   4:     <Value Type="Lookup">1</Value>
   5:   </Eq>
   6: </Where>

In order to do it in Camlex we introduced 2 new types for string-based syntax: DataTypes.LookupId and DataTypes.LookupValue. Previous DataTypes.Lookup was made internal (this is one more breaking change in 2.0 version). So CAML queries mentioned above can be created using the following code:

   1: // lookup value
   2: string query = Camlex.Query().Where(
   3:     x => x["Status"] == (DataTypes.LookupValue)"Completed").ToString();
   4:  
   5: // lookup id
   6: query = Camlex.Query().Where(
   7:     x => x["Status"] == (DataTypes.LookupId)"1").ToString();

4. Support for native System.Guid type for values

One more native type is supported in Camlex.NET 2.0: System.Guid. In previous version developers needed to cast variable of Guid type to string in order to specify Guid as value in CAML query ((DataTypes.Guid)val.ToString()). Now you can just pass Guid variable into expression:

   1: var guid = new Guid("a30bcb5a-c614-4dc0-8df0-8741e2aebfd9");
   2: string query = Camlex.Query().Where(x => (Guid)x["UniqueId"] == guid).ToString();

So with 2.0 you can specify Guid both in indexer and in value.

This was an overview of new features in Camlex.NET 2.0. Hope it will be useful in Sharepoint development and will allow developers to increase their productivity. Thanks to all guys who provide us feedback on Camlex (especially butaji, Olegas, mihailsamson and Dmitry Pichugin). We very appreciate your efforts and hope that you will provide us your feedback in the future as well. It allow us to make Camlex better!

1 comment: