views:

769

answers:

2

How do i dispose reference to the subWeb in the following query?

using (SPSite spSite = Utility.GetElevatedSite(_rootUrl))
{
    from SPWeb web in spSite.AllWebs
    where web.ServerRelativeUrl.ToLower() == path
    from SPWeb subWeb in web.Webs                            
    select subWeb
}

Do i even need to worry about disposing the subWeb if iam already wraped the spSite in the Using statement?

Edit:

Is it a good idea too call garbage collection in this scenario?

+6  A: 

Unfortunately, you do. The trouble starts from the SPSite.AllWebs property. The SPWeb.Web property isn't safe either.

Read this very thorough reference of the situations where you need to worry about disposing SharePoint objects.

http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx

(I suggest adding this to your SharePoint cheat-sheet).

As a result, I feel the current SharePoint object model can not be safely used with LINQ syntax.

Your code would need a re-write with the various implied loops broken out so that you can explicitly dispose the objects involved.

Edit: The SPDisposeCheck tool is a command-line console app that will scan your .NET assemblies and warn you of undisposed references based on the above best-practice guidelines. Check it out.

http://code.msdn.microsoft.com/SPDisposeCheck

http://blogs.msdn.com/sharepoint/archive/2008/11/12/announcing-spdisposecheck-tool-for-sharepoint-developers.aspx

John Liu
Sahrepoint aside, i take it thats its not possible to dipose any external resources when using linq in general, right?
The thing with SharePoint is that the managed part is very tiny. But the SPWeb and SPSite instances hold a reference to a unmanaged SPRequest object that could be 1-2MBs. When GC doesn't come around fast enough, SharePoint runs out of memory.
John Liu
My understanding is that when we use LINQ with DataContext, you do dispose on the entire datacontext after LINQ is done with it, which releases the external resources. So yes that would be my assumption that LINQ does not do dispose for the developer.
John Liu
Cheers mate, very useful stuff!
another question, would you ever call/use garbage collection in this scenario?
I doubt SPDisposeCheck would help here because the enumeration of the SPWebCollections occurs behind the closed doors of LINQ. All the tool would see are delegates that accept SPWeb references, which are 'safe'.
dahlbyk
A: 

The technical answer to your original question is a qualified "No": all SPWeb objects opened from an SPSite are automatically disposed when the SPSite is disposed. However, in practice it is a good idea to dispose an SPWeb as soon as you're done with it to reduce memory pressure, especially when working with code like this that opens several SPWeb objects.

Implementing this dispose-safe behavior for LINQ is actually quite simple in C#. You can find full details in this post, but the short version is that a C# iterator can handle disposal for you. Using my AsSafeEnumerable() extension method, your code is relatively safe written like this:

using (SPSite spSite = Utility.GetElevatedSite(_rootUrl))
{
    var sw = from SPWeb web in spSite.AllWebs.AsSafeEnumerable()
             where web.ServerRelativeUrl.ToLower() == path
             from SPWeb subWeb in web.Webs.AsSafeEnumerable()      
             select subWeb;
    foreach(SPWeb aSubWeb in sw)
    {
        // Do something
    }
}

Now the result of your query, which I've assigned to sw, is a lazy iterator of type IEnumerable<SPWeb>. As you enumerate over that result, each SPWeb will be disposed when the enumerator moves to the next item. This means that it is not safe to use that SPWeb reference, or any SP* object created from it (SPList, etc), outside of your foreach loop. Also sw would not be safe to use outside of that using block because the iterator's SPWebCollections will be tied to the now-disposed SPSite.

That said, code like this that enumerates over all webs (twice!) is extremely expensive. There is almost certainly a more efficient way that this could be implemented, if only by using spSite.AllWebs[path] instead of your from/where.

Regarding garbage collection, these objects require disposal because of unmanaged memory allocated that the GC doesn't even know about.

Finally, a word of caution about your 'GetElevatedSite' utility. If you're using RunWithElevatedPrivileges in your helper method to get your elevated SPSite, there are a number of issues you could run into by returning your SPSite out of that elevated context. If possible, I would suggest using SPSite impersonation instead - my preferred method is described here.

dahlbyk
Cheers buddy, very helpful indeed. I will typically [select into something] whenever i use a linq query. I cant imagine programming the object model without linq. THe IF branches would be serveral levels deep! But i suppose there are pro's/con's with both methods...
Certainly, I'm really starting to get into it to. As long as you select non-SharePoint stuff out you're fine, you just can't return objects dependent on the now-disposed web. And you can always add more LINQ below to query into lists and items. Enjoy!
dahlbyk