Home > SharePoint Development > Cross-Site Collection Query, Almost

Cross-Site Collection Query, Almost

This is a tale of getting close to one of SharePoint’s holy grails but not quite…

Site collections are the most scalable SharePoint container and they offer lots of advantages over building site heirarchies with layers of sub-sites. However information in one site collection can’t be made visible to another site collection using out-of-the-box SharePoint components. This is a major inconvenience when trying to aggregate content on a portal for example.

Solution (almost)

The solution evolves from first using the SPSiteDataQuery class to run a query upon each site collection and then aggregating the results.

The example below queries all Calendar lists for events within a date range:

// for each site collection
var currentApp = SPContext.Current.Site.WebApplication;
foreach (SPSite site in currentApp.Sites)
{
  var query = new SPSiteDataQuery()
  { 
    RowLimit = 100,
    Lists = @"<Lists ServerTemplate='106' />",
    Webs = "<Webs Scope='SiteCollection' />",
    Query =
        String.Format(
        @"<Where>
            <And>
              <Geq>
                <FieldRef Name='EventDate' />
                <Value Type='DateTime'>{0}</Value>
              </Geq>
              <Leq>
                <FieldRef Name='EndDate' />
                <Value Type='DateTime'>{1}</Value>
              </Leq>
            </And>
          </Where>",
          startDate.ToString("yyyy-MM-dd"), endDate.ToString("yyyy-MM-dd")),
    ViewFields =
        "<FieldRef Name='Title' />
         <FieldRef Name='ID' />
         <FieldRef Name='EventDate' />
         <FieldRef Name='EndDate' />
         <FieldRef Name='Location' />
         <FieldRef Name='Description' />
         <FieldRef Name='fAllDayEvent' />
         <FieldRef Name='fRecurrence' />
         <FieldRef Name='FileRef' />"
  };
  var results = site.RootWeb.GetSiteData(query);

  // aggregate the results
  ...
}

This will work, however it clearly doesn’t scale well as it will query many sites and webs each time it is run.

The next step was to consider the CrossListQueryCache class which provides the ability to cache the results. In reality, apart from introducing the cache, this class doesn’t do much more than wrap the SPSiteDataQuery class and the call to SPWeb.GetSiteData().

Continuing the same example, swap out the SPSiteDataQuery with:

var query = new CrossListQueryInfo
  {
    UseCache = true,
    RowLimit = 100,
    Lists = @"<Lists ServerTemplate='106' />",
    Webs = "<Webs Scope='SiteCollection' />",
    Query =
        String.Format(
        @"<Where>
            <And>
              <Geq>
                <FieldRef Name='EventDate' />
                <Value Type='DateTime'>{0}</Value>
              </Geq>
              <Leq>
                <FieldRef Name='EndDate' />
                <Value Type='DateTime'>{1}</Value>
              </Leq>
            </And>
          </Where>",
          startDate.ToString("yyyy-MM-dd"), endDate.ToString("yyyy-MM-dd")),
    ViewFields =
        "<FieldRef Name='Title' />
         <FieldRef Name='ID' />
         <FieldRef Name='EventDate' />
         <FieldRef Name='EndDate' />
         <FieldRef Name='Location' />
         <FieldRef Name='Description' />
         <FieldRef Name='fAllDayEvent' />
         <FieldRef Name='fRecurrence' />
         <FieldRef Name='FileRef' />"
  };

var cache = new CrossListQueryCache(query);
var results = cache.GetSiteData(site, site.RootWeb.ServerRelativeUrl);

Unfortunately attempting to use the cache for each site collection query causes problems – any attempt to query a site collection beyond the current one results in a error:

There is no Web named "/sites/WebSite". 

This is due to the way that CrossListQueryCache class is written – as revealed by examining Microsoft.SharePoint.Publishing with .NET Reflector. During the execution of the GetSiteData method, a call is made to the getWeb method of the ContentByQueryWebPart:

using (SPWeb web = ContentByQueryWebPart.getWeb(webUrl))
{
    return this.GetSiteData(web);
}

So when the cached query is executed it uses the getWeb method to get the reference to the SPWeb object to run the query against. The problem is that this method uses the context of the calling code, via SPContext, to open the web site specified by the URL:

internal static SPWeb getWeb(string webUrl)
{
    SPSite site = SPContext.Current.Site;
    SPWeb web = null;
    web = site.OpenWeb(webUrl);
    bool isRootWeb = web.IsRootWeb;
    return web;
}

Thus, even though the caller has passed through the correct SPSite to use when opening the SPWeb against which the query is to be run, the CrossListQueryCache ignores this and instead uses the context of the caller.

Close but no cigar…

The net result is that a cross-site collection query is possible using SPSiteDataQuery but clearly this could have a serious performance impact with querying multiple site collections and sub-sites. It would be nice to be able to have the query results cached however this doesn’t seem possible using CrossSiteQueryCache.

It’s somewhat infuriating that a relatively simple change to the CrossListQueryCache class would enable cached cross-site collection queries.

Advertisements
  1. Peter
    September 17, 2010 at 1:52 pm

    Hi

    Nice article, do you have any experiences with SP2010 or is that the same thing, I mean Cross-Site Collection Query.

    Thanks

  2. peterga
    March 4, 2011 at 2:28 pm

    Hi

    Have you tried this Query with Recurrence Events?

    Thanks

    • oidatsmyleg
      September 26, 2013 at 11:44 am

      No I haven’t sorry.

  1. September 25, 2013 at 11:06 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: