Linq to SharePoint: Query Across Site Collections?

The thoughts in this article are from an internal discussion about Linq to SharePoint.

Scenario

Say I have two site collections, https://sharepoint2010/sites/site1 and https://sharepoint2010/sites/site2, which are all based on team site template. Now I’d like to create a web part to query the announcements list of site2. I want my web part to always work no matter whether I put it on site1 or site2.

Linq to SharePoint is a preferred way to query data in SharePoint 2010. So I use it with the following piece of code.

 DataContext topLevelSite = new DataContext("https://sharepoint2010/sites/site2");   
EntityList<Item> annoucements = topLevelSite.GetList<Item>("Announcements");
var first3Items = from item in annoucements
      where item.Id <= 3
      select item;

When I put my web part on a page of site2, everything works fine. However, when I put my web part on a page of site1, an exception happens. The call stack is like the following one.

 Web at https://sharepoint2010/sites/site2 could not be found.
 at Microsoft.SharePoint.Linq.Provider.SPServerDataConnection..ctor(String url)
 at Microsoft.SharePoint.Linq.DataContext.get_DataConnection()
 at Microsoft.SharePoint.Linq.DataContext.<>c__DisplayClass2`1.<GetList>b__1()
 at Microsoft.SharePoint.Linq.DataContext.GetList[T](String listName, …)
 at Microsoft.SharePoint.Linq.DataContext.GetList[T](String listName)

Root Cause

The error of the above exception is very straightforward. When GetList<T> was called, an SPServerDataConnection object was initialized so that the query could be performed on a specific data connection. However, for some reason the constructor of SPServerDataConnection could not found the site I specified in my code.

To find out why, let us just reflect the constructor of SPServerDataConnection and see how it was implemented. We can use Reflector to reflect the assembly, Microsoft.SharePoint.Linq.dll, and the code of the constructor is as below.

 public void SPServerDataConnection(string url)
{
    if (SPContext.Current != null)
    {
        this.defaultSite = SPContext.Current.Site;
        this.defaultWeb = (SPContext.Current.Web.Url == url) 
            ? SPContext.Current.Web 
            : this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
    }
    else
    {
        this.defaultSite = new SPSite(url);
        this.defaultWeb = this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
    }
    if (!this.defaultWeb.Exists)
    {
        throw new ArgumentException
            (Resources.GetString("CannotFindWeb", new object[] { url }));
    }
    this.defaultWebUrl = this.defaultWeb.ServerRelativeUrl;
    this.openedWebs = new Dictionary<string, SPWeb>();
    this.openedWebs.Add(this.defaultWebUrl, this.defaultWeb);
}

According to the code, when we have a SPContext, the query will always be performed on the current site collection of the SPContext. In my code, the exception happened because the URL I passed in pointed to another site collection. It looks like we are not allowed to perform a query across site collections with Linq to SharePoint, just like what I wanted to do in my web part. 

Some Thoughts

So why SPServerDataConnection was designed to use SPContext.Current.Site, instead of using new SPSite(url) directly? Does it make sense? Well, at lease to me it does make sense because site collection should be a scope of custom queries. 

Do we really need a cross site collection query? I think this is a question related to the design of the SharePoint deployment. If you need to do a cross site collection query, you’d better review your design to see whether you really need multiple site collections, or just multiple sites are sufficient. When we decide whether we need a site collection or site, the following considerations are helpful.

  • Site collection is a scope for administrative privileges.
  • Site collection provides a scope for membership and security authorization.
  • Site collection provides a scope for backup and recovery.
  • Site collection provides a scope for many types of site elements.

Workaround

If in some special situation a cross site collection query is necessary, we can use the workaround discussed in the following thread.

https://social.technet.microsoft.com/Forums/sv-SE/sharepoint2010programming/thread/715ddcbb-619d-4688-8d99-d7a6aa078307

In short, this workaround creates a SPSite and SPWeb manually with the expected URL (https://sharepoint2010/sites/site2) and set the context web to the SPWeb, then initialize the DataContext object to perform the query.

Although it works, it requires special housecleaning. Especially you have to manage the SPContext by yourself. So be careful.