Sunday, October 2, 2011

How to make LINQ query to Sharepoint 2010 across Site Collections

 I'm currently member in a development team, working on a project on Sharepoint 2010, and one of the tasks is to execute LINQ query on Sharepoint objects, that's ok, seems quite simple as we have experience on LINQ.

But when we tried to apply the same query across site collections we realised that despite the fact that the correct site collection is opened, we couldn't read the objects.

During searching on the internet we found a workarround by changing the SPContext before executing the LINQ query.

In the webpart itself the HttpContext is stored in a variable before executing our search method.


We have found the cause of this problem. When an SPContext is available (like in the webpart), the constructor of SPServerDataConnection tries the open the url in the SPSite of the context.

So querying a linq datasource in a different site collection doesn't work whenever an SPContext object is available.

We managed to reproduce this problem in our console application by createing a fake SPContext object which causes the same error.

Microsoft.Sharepoint.Linq.Provider - SPServerDataConnection:

public 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();
    this.openedWebs.Add(this.defaultWebUrl, this.defaultWeb);
}

So, we tried to cheat on the application, below you can see the solution:

In the webpart itself the HttpContext is stored in a variable before executing our method.

SPSecurity.RunWithElevatedPrivileges(delegate()
{
     using (SPSite contextSite = new SPSite("http://"))
     {
          using (SPWeb contextWeb = contextSite.OpenWeb())
          {
               HttpRequest httpRequest = new HttpRequest("", contextWeb.Url, "");
               HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
               SPControl.SetContextWeb(HttpContext.Current, contextWeb);               
               using (MyLinqDataContext dc = new MyLinqDataContext("http://"))
               {
                    EntityList entities = dc.GetList("");
                    {
                    }
               }
           }
     }
}


After performing the query we set the HttpContext back to the original HttpContext. 


tempContext = HttpContext.Current;
this.Search();
HttpContext.Current = tempContext;


I'm not sure if this workaround is appropriate, but at least we can now perform our cross site collection LINQ query in our webpart.