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, http://sharepoint2010/sites/site1 and http://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("http://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 http://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.

http://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 (http://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. 

Comments (4)

  1. Roy Higgs says:

    Hi, thank you for the great post. It answers my question as to why my Linq to SharePoint is not working. I do have to strongly disagree with you as there are many valid cases for querying data across site collections. Without spending time documenting a bunch of valid business requirements we can easily determine that this is absolutely terrible code by the simple fact that the contructor accepts a URL. If the constructor accepts that URL it should use that URL with no exceptions. Otherwise the contract is broken. If they want to provide a way to use the current context then provide a constructor with no parameters.

    This is really just absolutely terrible. Sigh. Back to the CAML queries….

  2. Chun Liu says:

    It is debatable whether we really need a cross site collection query. But at least I think the documentation of this interface in SDK can be improved.

  3. Barton Mathis says:

    I totally agree with Roy.  There are many legitimate reasons for querying across site collections, or even web apps.  But that's really not the point.  The point is that code that behaves inconsistently is simply bad design. As is code that completely ignores parameters.  This is not a documentation problem – it's a "bad code" problem.

Skip to main content