Now that you've been putting a lot of data
into your SharePoint server, what are the facilities available to you
that allow you to query this data from SharePoint?
There are many ways to pull data out of SharePoint.
You can use the object model and get hold of the SPList object, and run
a for/each over SPListItems. Of course, that isn't the smartest way to
filter for data, though. Filtering via iteration can be extremely
resource-expensive.
You could use Search, even programmatically. You can
do so by using the FullTextSqlQuery object as demonstrated in an
article you can read at http://blah.winsmarts.com/2008-2-SharePoint_Search_ranking_rules_and_running_it_programatically.aspx.
While that will provide you with results quickly, it may or may not
provide you with accurate results, and certainly has an external
dependency on crawl schedules and algorithms. Given the nature of
search, you also have limited control over the sorting mechanisms. The
sorting is controlled generally by the rank, which is dependent on an
algorithm that you can influence, but not fully control. Filtering or
querying is more definitive, results are more accurate, and they appear
instantaneously. Search requires indexing, may not pinpoint the actual
results, but it's less accurate nature, allows the user to execute a
search, without knowing exactly what they're looking for. Specifically
here we will be talking about querying filtering, not search.
1. Querying Using CAML
CAML, the Collaborative Application Markup Language,
is used for many purposes within SharePoint, one of which is extracting
the very data you need and striking an excellent balance between speed,
dependability, and accuracy.
Within SharePoint, there are a number of objects
that make use of CAML and can help you query for data. You would define
your CAML query using a syntax as follows:
<Query>
<Where>
<And ...
<BeginsWith ..
<Eq ..
<Contains ..
<Geq ..
... etc.
<Or ...
</Where>
</Query>
You can find a full reference to the CAML syntax at http://msdn.microsoft.com/en-us/library/ms467521.aspx.
Once you do write the CAML query, there are numerous
ways to execute this query on your SharePoint server. You can choose to
execute this query using the lists web service available at
/_vti_bin/lists.asmx. Listing 1 shows an easy way to filter out all rows modified by a given user id, using the lists.asmx web service.
Example 1. Using lists.asmx to Filter Out Rows Modified by a Certain User
XmlDocument camlDocument = new XmlDocument(); camlDocument.LoadXml( @"<Query> <Where> <Or> <Eq> <FieldRef Name='Author' /> <Value Type='User'> Winsmarts\Administrator </Value> </Eq> <Eq> <FieldRef Name='Editor' /> <Value Type='User'> Winsmarts\Administrator </Value> </Eq> </Or> </Where> </Query>");
using (SP2010.Lists ws = new SP2010.Lists()) { ws.Credentials = System.Net.CredentialCache.DefaultCredentials; ws.Url = "http:// SP2010/_vti_bin/lists.asmx";
XmlNode xView = camlDocument.CreateNode( XmlNodeType.Element, "ViewFields", ""); XmlNode xQryOpt = camlDocument.CreateNode( XmlNodeType.Element, "QueryOptions", "");
//query the server XmlNode xNode = ws.GetListItems( "Announcements", "", camlDocument.ChildNodes[0], xView, "", xQryOpt, ""); } </div>
|
Web services have their advantages, but performance
and XmlSerialization isn't one of them. So it is reasonable to expect
that you have very rich support for CAML in the object model as well.
At the heart of it are the SPQuery object and the SPSiteDataQuery
object. The SPQuery object and SPSiteDataQuery object are quite similar
to each other, except that the SPSiteDataQuery object has the
capability to query over an entire site collection. Starting with
SharePoint 2010, you can also perform joins between various lists using
CAML.
I'll illustrate this with the help of a simple
example. You first need to set
up some sample data. To set up the sample data, find the "Raw Data.wsp"
solution package in your associated code download and then upload and
activate it in the solution gallery. Activating the solution will give
you a new site definition called "Raw Data". Go ahead and create a
subsite at http://sp2010/sampledata
based on the "Raw Data" site definition. This newly created site will
give you two lists: Artists and Songs. Specifically, the Songs list
will have a lookup column pointing to the artist list. This lookup
column will allow me to demonstrate the capability of CAML to perform
joins between lists.
The sample data for the artist's lists can be seen in Figure 1.
And the sample data for the Songs list can be seen in Figure 2.
As you can see, I'm pulling in both the Title and
the Country column from the artist's list into the Songs list. Next,
create a simple SharePoint console application using Visual Studio
2010. I will first demonstrate the ability to query using the SPQuery
object without doing a join. What I intend to do is return every song
whose artist's name contains a specified input parameter. Because the
Artist.title has been pulled into the Songs list, I can perform such a
query on the Songs list. Also I will project this data back into a
custom business object. Start by adding a class into your console
application project, called Song. This class can be seen as –follows:
public class Song
{
public string SongName { get; set; }
public int SongID { get; set; }
public string Country { get; set; }
}
Next, add a method in your console application, as shown in Listing 2.
Example 2. Querying on a Single List Using CAML and SPQuery
public static List<Song> GetSongsByArtist(string artistContainsText) { List<Song> songs = new List<Song>();
XmlDocument camlDocument = new XmlDocument(); camlDocument.LoadXml( @"<Where> <Contains> <FieldRef Name='Artist' /> <Value Type='Lookup'>[artistContainsText]</Value> </Contains> </Where>".Replace("[artistContainsText]", artistContainsText));
using (SPSite site = new SPSite(siteUrl)) { SPWeb web = site.OpenWeb();
SPQuery query = new SPQuery(); query.Query = camlDocument.InnerXml;
SPListItemCollection items = web.Lists["Songs"].GetItems(query);
IEnumerable<Song> sortedItems = from item in items.OfType<SPListItem>() orderby item.Title select new Song { SongName = item.Title, SongID = item.ID };
songs.AddRange(sortedItems); }
return songs; }
|
As you can see from Listing 2,
I'm executing a CAML query and I'm using a "contains" clause to filter
out all items where the Artist column contains the specified input word
"artistContainsText". I then execute this query on the Songs list using
the GetItems method. Finally I execute a LINQ query to create a
projection of this data in my custom business object, which I return
from this method.
Thus the usage of this method becomes really simple:
List<Song> songsMichael = GetSongsByArtist("Michael");
foreach (var song in songsMichael)
{
Console.WriteLine(song.SongName);
}
Running this code should show an output as shown in Figure 3.
As you can see, all the songs are either from Michael Jackson or George Michael. Both of them have the word Michael in their name, so both of their songs are matched.
This is great, but sometimes you do not have the
projected fields available in the related lists. In those scenarios, it
is very useful to be able to perform joins between these lists. The
ability to create joins is a little bit different from joins in an SQL
server. Joins in SharePoint are driven off of lookup-based columns. In
order to support joins in CAML queries, CAML queries in SharePoint 2010
and their associated objects have been enhanced to support these:
Next let us look at an example, in which I will
query the Songs list for all songs whose title contains an input
string. Then I will use joins and create projected fields from the
Artist list to show which song comes from which country. You can
achieve this by using the code shown in Listing 3.
Example 3. Ability to Create Joins Using the SPQuery object and CAML
public static List<Song> GetSongsByName(string titleContainsText) { List<Song> songs = new List<Song>();
XmlDocument camlDocument = new XmlDocument(); camlDocument.LoadXml( @"<Where> <Contains> <FieldRef Name='Title' /> <Value Type='Text'>[titleContainsText]</Value> </Contains> </Where>".Replace("[titleContainsText]", titleContainsText));
using (SPSite site = new SPSite(siteUrl)) { SPWeb web = site.OpenWeb();
SPQuery query = new SPQuery(); query.Query = camlDocument.InnerXml; query.Joins = @" <Join Type='LEFT' ListAlias='Artist'> <Eq> <FieldRef Name='Artist' RefType='Id'/> <FieldRef List='Artist' Name='ID'/> </Eq> </Join> ";
query.ProjectedFields = @" <Field Name='Country' Type='Lookup' List='Artist' ShowField='Country'/> ";
SPListItemCollection items = web.Lists["Songs"].GetItems(query);
IEnumerable<Song> sortedItems = from item in items.OfType<SPListItem>() orderby item.Title select new Song { SongName = item.Title, SongID = item.ID, Country = item["Country"].ToString()};
songs.AddRange(sortedItems); } return songs; }
|
As you can see from Listing 3,
I'm embellishing a simple CAML query with a specified join and
specified projected fields. Then, as before, I'm executing a LINQ query
to create a projection of this data into my custom business object, and
I am returning that custom business object from this method. This code
can be executed as shown here:
List<Song> songsA = GetSongsByName("A");
foreach (var song in songsA)
{
Console.WriteLine("{0} is from {1}", song.SongName, song.Country);
}
Executing the preceding code should show the output shown in Figure 4.
As you can see, not only are you showing
the titles of the songs but you can also perform a join with the
artist's lists and show which country the song came from. Nevertheless,
there are some things I don't like about this code. For one, it shows
that weird ID that looks like 1;#. Then the CAML queries aren't a lot
of fun to write, and the errors I make in CAML queries will be caught
at runtime, not compile time. Is there a better way?